Skip to content

More AsyncIterator helper functions #599

Closed as not planned
Closed as not planned
@Qelxiros

Description

@Qelxiros

Proposal

Problem statement

Working with AsyncIterators is currently very difficult due to a lack of helper functions (Iterator provides 75 methods, while AsyncIterator provides only size_hint).

Motivating examples or use cases

This block of code (from https://github.com/Kobzol/async-iterator-examples/blob/25e239bd708943f4971366b591a99e918771e030/json-line-parser/src/bin/server.rs#L67-L76)

let mut counter = 0;
    while let Some(msg) = poll_fn(|cx| iter.as_mut().poll_next(cx)).await {
        match msg {
            Message::Ping => {}
            Message::Hello(_, count) => {
                counter += count;
            }
        }
    }
    Ok(counter)

would be absurd to write with a standard Iterator. It would be much more idiomatic and readable to write something like this:

let counter = iter.filter_map(|msg| match msg {
    Message::Ping => None,
    Message::Hello(_, count) => Some(count),
}).sum::<u32>();
Ok(counter)

Depending on which methods are available on Message, it might be desirable to eliminate the match statement in favor of a filter and a map separately. There are a plethora of more readable options due to Iterator's helper functions, and it's a big part of what makes working with Iterators in Rust so enjoyable.

Solution sketch

There are many useful methods on Iterator, but these seem like a reasonable subset with which to start. I included fuse() here because its use case is mentioned in the documentation, which is how I got here. I also added fold() because (at least in my experience) while it's not the most common adapter, it's a very powerful one, and one through which many others are implemented.

Note that the returned structs are not the same as the ones for Iterator and implement AsyncIterator instead.

impl AsyncIterator {
    // Item, poll_next, size_hint

    fn fuse(self) -> Fuse<Self> where Self: Sized;
    async fn fold<B, F>(self, init: B, f: F) -> B where Self: Sized, F: FnMut(B, Self::Item) -> B;
    async fn count(self) -> usize;
    fn map<B, F>(self, f: F) -> Map<Self, F> where Self: Sized, F: FnMut(Self::Item) -> B;
    fn filter<P>(self, predicate: P) -> Filter<Self, P> where Self: Sized, P: FnMut(&Self::Item) -> bool;
    fn filter_map<B, F>(self, f: F) -> FIlterMap<Self, F> where Self: Sized, F: FnMut(Self::Item) -> Option<B>;
}

Alternatives

  • The methods that take closures could take async closures. I'm not sure which way is better, feedback would be great.
  • This change could be postponed until AsyncIterator is stabilized. This seems bad because more people are likely to use it if it's ergonomic.
  • A different subset of Iterator helper functions could be chosen, or new async-specific functions could be designed. I have no strong opinions on this either.

Links and related work

What happens now?

This issue contains an API change proposal (or ACP) and is part of the libs-api team feature lifecycle. Once this issue is filed, the libs-api team will review open proposals as capability becomes available. Current response times do not have a clear estimate, but may be up to several months.

Possible responses

The libs team may respond in various different ways. First, the team will consider the problem (this doesn't require any concrete solution or alternatives to have been proposed):

  • We think this problem seems worth solving, and the standard library might be the right place to solve it.
  • We think that this probably doesn't belong in the standard library.

Second, if there's a concrete solution:

  • We think this specific solution looks roughly right, approved, you or someone else should implement this. (Further review will still happen on the subsequent implementation PR.)
  • We're not sure this is the right solution, and the alternatives or other materials don't give us enough information to be sure about that. Here are some questions we have that aren't answered, or rough ideas about alternatives we'd want to see discussed.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions