Help test async/await/generators/coroutines!

Good evening/morning Rustlers!

The saga of generators that started long long ago recently made progress as we concluded that we should land an implementation in the compiler and start experimenting (but not stabilizing). Even more recently the heroic efforts of @Zoxc and many others has culminated in an epic PR to implement coroutines in rustc. Now that we’ve made it this far, we need your help to go farther!

The purpose of the eRFC for coroutines was to provide the ability for the Rust community to test out generators on the nightly channel of the compiler. This feedback and testing will be the driving force for stabilization for this language feature and it’s absolutely essential!

One of the main driving factors for implementing coroutines has been async/await syntax for the futures crate, and so we’re also very eager to have you test that out as well! You’ll not only exercise the generators language feature but also conservative_impl_trait and the fledgling proc_macro “macros 2.0” ecosystem. We’re very eager to get feedback on all of these features as we approach stabilization!

If you’d like to get started here’s some helpful links:

I’m quite eager to hear everyone’s feedback on all these features and goodies, please give them a spin and let us know how it goes! The next step for generators will be an RFC for stabilization, but we’re still aways away from that. With your help, though, we can try to approach it more quickly!

53 Likes

We salute you @Zoxc! You actually did it!

11 Likes

:heart: Great job guys!

I’m personally very interested in blocking style APIs, but given that self-referential types are yet to come I’ll have to come up with a different design. I wonder if anyone has a good idea on how to approach it.

The problem with blocking style API is that we basically want this:

impl TcpStream {
    fn read(&self, buf: &mut [u8]) -> Async<io::Result<usize>> { ... } 
}

But it’s not yet possible to make it work. Any ideas?

What do you mean by “it’s not yet possible to make it work”? That looks to me like exactly what futures-rs enables, with this new development making the method body nicer.

I guess it’s because of the references in the arguments - impossible for now.

Not exactly, while current iteration of futures-await helps with using futures and allows you to write less spaghetti code, you are still limited. For example:

fn handle_conn(mut conn: Conn) -> impl Async<()> {
    let mut buf = [0; 1024];

    loop {
        let n = await!(conn.read(&mut buf)).unwrap();
        if n == 0 {
            return;
        }
        await!(conn.write_all(&buf[0..n]));
    }
}

(Where Async<T> is Generator<Yield=T, Return=()>) It is not possible to make this example compile. The problem here is with &mut buf and &mut Conn. After the generator is transformed into a state machine it will have both Conn and &mut Conn as its struct fields, the same for buf. We don’t have self-referential types yet, so it won’t compile.

The ideal API for me is smth like this: https://github.com/rozaliev/async-await-rs/blob/master/src/apps/echo.rs

Ah, yes, you’re completely right. This is a pretty long-standing issue, with lots of additional use cases. My favorite solution is described in @Zoxc’s immovable types RFC. That would enable generators that take pointers to their local state and keep them across suspension points, while still allowing them to be moved around by value (as in -> impl Future) before they are first run.

Another solution might be to use nonblocking IO, as I tried to demonstrate here.
(reader's fd must be put into non-blocking mode before calling copy(), of course).

My experience with impl Trait so far is that the compile time is insane, possibly due to a bug not being resolved for a while. My API client is still taking 5 minutes to compile on nightly.

:heart: Amazing job guys.


I’ve implemented #[async] / await! in Shio’s examples and saying it’s significantly better feels like the understatement of the year. Compare the postgres example on the master branch with the await branch.

6 Likes

<good-part>

This is really awesome. I wasn’t expecting this to be implemented for another year or so :smile: Hopefully this will make Rust/Tokio much more feasible for real-life production usage.

My favorite recursive Fibonacci sequence example works fine (yay!)

</good-part> <bad-part>

But there seems to be some problem with generator objects size calculation when the generator invokes itself. Naturally, a generator cannot store a copy of itself on the stack across yield points (that would make its size infinite), but it should work fine to store itself unboxed between yield points (this works) and to store itself boxed across yield points (this causes rustc to overflow its stack).

And when I try to cast the boxed-self to a boxed trait object, rustc reports a weird lifetime error.

Playground link

</bad-part>

1 Like

But there seems to be some problem with generator objects size calculation when the generator invokes itself.

Sounds related to impl trait: rustc stack overflow · Issue #35706 · rust-lang/rust · GitHub

Oh dear sounds bad! Do you know if there's a bug filed for this against rust-lang/rust?

Thanks for the report! I'll make sure we track the bug @dwrensha cc'd

AFAIK #43787 is suspected, but I haven't profiled to determine the cause.

An interesting thing about this is that the code compiles in 20 seconds for the lib, but insanely long for any binary that uses it (tests, CLI bin).

Probably https://github.com/rust-lang/rust/issues/38528.

I think using generators to implement iterators will be a huge benefit. For example this playground shows an example of an iterator that is very simple to implement with a generator, but otherwise somewhat complex and/or unsafe.

The eRFC discusses using wrapper types to convert generators to iterators. (That’s also the approach used in my playground above.) It mentions that wrapper types may be necessary because a generic impl<G: Generator> Iterator for G could cause coherence errors. But as an alternative to wrapper types, would it be possible for the compiler to automatically generate both Iterator and Generator implementations for each generator literal?

7 Likes

One could likewise hope for impl<I: Iterator> Generator for I – could specialization allow us to implement a mutual relationship like this?

From my preliminary tests I’ve seen that a generator-converted-to-iterator in some cases is much faster (and leads to cleaner code) than Iterator::scan().

But I think performance of such iterators-from-generators still needs to be assessed.

Can you use that on this function too?

// pairwise(1 2 3) => (1, 1) (1, 2) (1, 3) (2, 2) (2, 3) (3, 3)
fn pairwise<'a, T>(items: &'a [T]) -> impl Iterator<Item=(&'a T, &'a T)> + 'a {
    items
    .iter()
    .enumerate()
    .flat_map(move |(i, x1)| items[i ..]
                             .iter()
                             .map(move |x2| (x1, x2)))
}