Async iterator: why not both poll_next and async next?

Rust's async iterator could be defined like this

#[required(or(poll_next, next))]
pub trait AsyncIterator {
    type Item;

    // required methods: either poll_next or next

    fn poll_next(
        self: Pin<&mut Self>,
        cx: &mut Context<'_>
    ) -> Poll<Option<Self::Item>>;

    async fn next(&mut self) -> Option<Self::Item>;

    // Provided method
    fn size_hint(&self) -> (usize, Option<usize>);
}

And, using an idea from Haskell, the trait implementer can define the trait either using poll_next or next; if one is missing, it is automatically defined in terms of the other (in the trait definition, they are mutually recursive, but one of them is required, in order to tie the knot).


This idea doesn't require maybe async at all, but,

Even if Rust ends up having effects or "maybe async", extending the already existing Iterator trait, having both methods is this is still possible: just define that while the next method is maybe async (as expected), the poll_next method exists only if the trait is async (but it isn't itself async).

Something like this (made up syntax)

#[required(or(poll_next, next))]
pub trait async<A> Iterator {
    type Item;

    // required methods: either poll_next or next

    #[if_async<A>] // poll_next exists only when the trait is async
    fn poll_next(
        self: Pin<&mut Self>,
        cx: &mut Context<'_>
    ) -> Poll<Option<Self::Item>>;

    async<A> fn next(&mut self) -> Option<Self::Item>;

    // Provided method
    fn size_hint(&self) -> (usize, Option<usize>);

    // many other provided methods
}

In this case, one can either impl a type for Iterator using next and async Iterator using poll_next (which is the most low level way, which also enables using async-only constructs in the async implementation), or they can impl a type for both sync and async iterators at the same time (which requires implementing maybe async next and not using async-only constructs)

Something like this

struct S;

// separate implementations for both `Iterator` and `async Iterator`

impl Iterator for S {
    fn next(..) ..
}

impl async Iterator for S {
    fn poll_next(..) ..

    // or async fn next(...) ..
}

or

// a single implementation for both sync and async iterators
impl async<A> Iterator for S {
    async<A> fn next(...) ...
}
2 Likes

Note: this idea is kind of a comment on a number of blog posts;

How would you define poll_next in terms of next? AFAIK the problem is that next returns the state in a separate Future, while poll_next must hold it internally inside self.

Also, next is not object safe, while poll_next is.

1 Like

@traviscross has a concrete proposal for defining these two models in terms of each other. It can be done, but it definitely requires more complexity than just making them both methods of the same trait with default implementations.

2 Likes

Oh, I just assumed it would be possible..

That's interesting! Did he post somewhere?