Tracking issue for RFC 2033: Experimentally add coroutines to Rust

c43f5ce
Opened by Aaron Turon at 2025-04-15 21:11:13

RFC.

This is an experimental RFC, which means that we have enough confidence in the overall direction that we're willing to land an early implementation to gain experience. However, a complete RFC will be required before any stabilization.

This issue tracks the initial implementation.

related issues

  1. cc https://github.com/rust-lang/rust/pull/43076, an initial implementation

    Alex Crichton at 2017-07-09 04:27:10

  2. Copied from #43076:


    I'm using this branch for stream-heavy data processing. By streams I mean iterators with blocking FS calls. Because Generator is missing an Iterator or IntoIterator implementation, you must call your own wrapper. Zoxc kindly provided an example, but it's quite unergonomic. Consider:

    Python:

    def my_iter(iter):
        for value in iter:
            yield value
    

    Rust with generators:

    fn my_iter<A, I: Iterator<Item=A>>(iter: I) -> impl Iterator<Item=A> {
        gen_to_iter(move || {
            for value in iter {
                yield value;
            }
        })
    }
    

    Two extra steps: inner closure + wrapper, and, worse, you have to write the wrapper yourself. We should be able to do better.

    TL:DR: There should be a built-in solution for GeneratorIterator.

    Nelo Mitranim at 2017-07-25 07:46:38

  3. I was a bit surprised that, during the RFC discussion, links to the C++ world seemed to reference documents dating back from 2015. There have been some progress since then. The latest draft TS for coroutines in C++ is n4680. I guess the content of that draft TS will be discussed again when the complete RFC for Rust's coroutines is worded, so here are some of the salient points.

    First, it envisions coroutines in a way similar to what this experimental RFC proposes, that is, they are stackless state machines. A function is a coroutine if and only if its body contains the co_await keyword somewhere (or co_yield which is just syntactic sugar for co_await, or co_return). Any occurrence of co_await in the body marks a suspension point where control is returned to the caller.

    The object passed to co_await should provide three methods. The first one tells the state machine whether the suspension should be skipped and the coroutine immediately resumed (kind of a degenerate case). The second method is executed before returning control to the caller; it is meant to be used for chaining asynchronous tasks, handling recursive calls, etc. The third method is executed once the coroutine is resumed, e.g. to construct the value returned by the co_await expression. When implementing most generators, these three methods would have trivial bodies, respectively { return false; }, {}, and {}.

    Various customization mechanisms are also provided. They tell how to construct the object received by the caller, how to allocate the local variables of the state machine, what to do at the start of the coroutine (e.g. immediately suspend), what to do at the end, what do to in case of an unhandled exception, what to do with the value passed to co_yield or co_return (how yielded values are passed back to the caller is completely controlled by the code).

    Guillaume Melquiond at 2017-08-21 17:32:51

  4. One subtle point that came up is how we handle the partially-empty boxes created inside of box statements with respect to OIBITs/borrows.

    For example, if we have something like:

    fn foo(...) -> Foo<...> {}
    fn bar(...) -> Bar<...> {}
    box (foo(...), yield, bar(...))
    

    Then at the yield point, the generator obviously contains a live Foo<...> for OIBIT and borrow purposes. It also contains a semi-empty Box<(Foo<...>, (), Bar<...>)>, and we have to decide whether we should have that mean that it is to be treated like it contains a Box, just the Foo<...>, or something else.

    Ariel Ben-Yehuda at 2017-09-20 21:08:55

  5. I might be missing something in the RFC, but based on the definition of resume in the Generator struct, and the given examples, it looks like these generators don't have two way communication. Ideally this language construct would allow us to yield values out and resume values into the generator.

    Here's an example of implementing the async/await pattern using coroutines in ES6. The generator yields Promises and the coroutine resumes the generator with the unwrapped value of a Promise each time the Promise completes. There is no way this pattern could have been implemented without the two-way communication.

    Rust has a problem here because what's the type of resume? In the ES6 example, the generator always yields out some kind of Promise and is always resumed with the unwrapped value of the Promise. However the contained type changes on each line. In other words, first it yields a Promise<X> and is resumed with an X, and then it yields a Promise<Y> and is resumed with a Y. I can imagine various ways of declaring that this generator first yields a Wrapper<X> and then a Wrapper<Y>, and expects to be resumed with an X and then a Y, but I can't imagine how the compiler will prove that this is what happens when the code runs.

    TL;DR: yield value is the less interesting half. It has the potential to be a much more ergonomic way to build an Iterator, but nothing more.

    let resumedValue = yield value; is the fun half. It's what turns on the unique flow control possibilities of coroutines.

    (Here are some more very interesting ideas for how to use two-way coroutines.)

    masonk at 2018-01-07 06:47:22

  6. @arielb1

    Then at the yield point, the generator obviously contains a live Foo<...> for OIBIT and borrow purposes. It also contains a semi-empty Box<(Foo<...>, (), Bar<...>)>, and we have to decide whether we should have that mean that it is to be treated like it contains a Box, just the Foo<...>, or something else.

    I don't know what you mean by "OIBIT". But at the yield point, you do not have a Box<(Foo<...>, (), Bar<...>)> yet. You have a <Box<(Foo<...>, (), Bar<...>)> as Boxed>::Place and a Foo<...> that would need to be dropped if the generator were dropped before resuming.

    Michael Hewson at 2018-01-19 18:51:23

  7. Looking at the API, it doesn't seem very ergonomic/idiomatic that you have to check if resume returns Yielded or Complete every single iteration. What makes the most sense is two methods:

    fn resume(&mut self) -> Option<Self::Yield>;
    fn await_done(self) -> Self::Return;
    

    Note that this would technically require adding an additional state to closure-based generators which holds the return value, instead of immediately returning it. This would make futures and iterators more ergonomic, though.

    I also think that explicitly clarifying that dropping a Generator does not exhaust it, stopping it entirely. This makes sense if we view the generator as a channel: resume requests a value from the channel, await_done waits until the channel is closed and returns a final state, and drop simply closes the channel.

    Clar Fon at 2018-02-11 00:37:23

  8. Has there been any progress regarding the generator -> iterator conversion? If not, is there any active discussion about it somewhere? It would be useful to link it.
    @Nemikolh and @uHOOCCOOHu, I'm curious about why you disagree with @clarcharr's suggestion. Care to share your thoughts?

    Jan Hohenheim at 2018-02-28 19:50:19

  9. Has there been any progress regarding the generator -> iterator conversion? If not, is there any active discussion about it somewhere?

    https://internals.rust-lang.org/t/pre-rfc-generator-integration-with-for-loops/6625

    Nikita at 2018-03-02 09:40:14

  10. I was looking at the current Generator-API and immediately felt uneasy when I read

    If Complete is returned then the generator has completely finished with the value provided. It is invalid for the generator to be resumed again.

    Instead of relying on the programmer to not resume after completion, I would strongly prefer if this was ensured by the compiler. This is easily possible by using slightly different types:

    pub enum GeneratorState<S, Y, R> {
        Yielded(S, Y),
        Complete(R),
    }
    
    pub trait Generator where Self: std::marker::Sized {
        type Yield;
        type Return;
        fn resume(self) -> GeneratorState<Self, Self::Yield, Self::Return>;
    }
    

    (see this rust playground for a small usage example)

    The current API documentation also states:

    This function may panic if it is called after the Complete variant has been returned previously. While generator literals in the language are guaranteed to panic on resuming after Complete, this is not guaranteed for all implementations of the Generator trait.

    So you might not immediately notice a resume-after-completion at runtime even when it actually occurs. A panic on resume-after-completion needs additional checks to be performed by resume, which would not be necessary with the above idea.

    In fact, the same idea was already brought up in a different context, however, the focus of this discussion was not on type safety.

    I assume there are good reasons for the current API. Nevertheless I think it is worth (re)considering the above idea to prevent resume-after-completion. This protects the programmer from a class of mistakes similar to use-after-free, which is already successfully prevented by rust.

    Toni Dietze at 2018-03-28 13:22:17

  11. I too would have preferred a similar construction for the compile time safety. Unfortunately, that construction doesn't work with immovable generators, once they have been resumed they can't ever be passed by value. I can't think of a way to encode that constraint in a similar way for pinned references, it seems you need some kind of affine reference that you can pass in and recieve back in the GeneratorState::Yielded variant rather than the current lifetime scoped Pin reference.

    Nemo157 at 2018-03-28 13:34:01

  12. A resume/await_done version seems much more ergonomic than moving the generator every time resume is called. And plus, this would prevent all of @withoutboats' work on pinning from actually being applied.

    Clar Fon at 2018-03-28 16:10:52

  13. Note that Iterator has a similar constraint- it's not really a big deal, it doesn't affect safety, and the vast majority of users of the trait don't even have to worry about it.

    Russell Johnston at 2018-03-28 17:31:40

  14. Question regarding the current experimental implementation: Can the yield and return types of generators (move-like syntax) be annotated? I would like to do the following:

    use std::hash::Hash;
    
    // Somehow add annotations so that `generator` implements
    // `Generator<Yield = Box<Hash>, Return = ()>`.
    // As of now, `Box<i32>` gets deduced for the Yield type.
    let mut generator = || {
        yield Box::new(123i32);
        yield Box::new("hello");
    };
    

    Daniel Hauser at 2018-04-05 09:16:14

  15. I was hopeful that let mut generator: impl Generator<Yield = Box<Debug>> = || { ... }; might allow this, but testing with

    fn foo() -> impl Generator<Yield = Box<Debug + 'static>> {
        || {
            yield Box::new(123i32);
            yield Box::new("hello");
        }
    }
    

    it seems the associated types of the return value aren't used to infer the types for the yield expression; this could be different once let _: impl Trait is implemented, but I wouldn't expect it to be.

    (Note that Hash can't be used as a trait object because its methods have generic type parameters which must go through monomorphization).

    One terrible way to do this is to place an unreachable yield at the start of the generator declaring its yield and return types, e.g.:

    let mut generator = || {
        if false { yield { return () } as Box<Debug> };
        yield Box::new(123i32);
        yield Box::new("hello");
    };
    

    EDIT: The more I look at yield { return () } as Box<Debug> the more I wonder how long till Cthulu truly owns me.

    Nemo157 at 2018-04-05 09:35:01

  16. Yeah, I was hoping as well impl Trait would do the trick, but couldn't get it to work either. Your if false { yield { return () } as Box<Debug> }; hack does indeed work, though after seeing that, I don't think I will be able to sleep for tonight.

    I guess the only way is to introduce more syntax to annotate the types?

    Daniel Hauser at 2018-04-05 10:43:02

  17. Will the Generator::resume() method be changed to use Pin<Self> and be safe, or is the idea to add a new SafeGenerator trait?

    Diggory Blake at 2018-04-05 18:09:54

  18. I assumed that it would be changed, and I happened to be looking at the Pin RFC just now and noticed that it agrees, but it is blocked on object safety of arbitrary self types (which is currently an open RFC):

    Once the arbitrary_self_types feature becomes object safe, we will make three changes to the generator API:

    1. We will change the resume method to take self by self: Pin<Self> instead of &mut self.
    2. We will implement !Unpin for the anonymous type of an immovable generator.
    3. We will make it safe to define an immovable generator.

    The third point has actually happened already, but it doesn't help much since that required making Generator::resume unsafe.

    Nemo157 at 2018-04-07 20:43:38

  19. I've found a use case that suggests that the Yield associated type should be parameterised by a lifetime (and thus rely on GATs). The trait would then look like this (with explicit lifetimes for clarity):

    pub trait Generator {
        type Yield<'a>;
        type Return;
        unsafe fn resume<'a>(self: Pin<'a, Self>) -> GeneratorState<Self::Yield<'a>, Self::Return>;
    }
    

    This version of the trait allows a generator to yield a reference to a local variable or other variables constrained by the lifetime of the generator.

    Dylan Ede at 2018-04-12 13:21:40

  20. What's curious is that currently on nightly you can write a generator that yields a reference to a local variable, but not use it. As soon as you put in a call to resume, compilation fails.

    Dylan Ede at 2018-04-12 13:36:39

  21. @dylanede that is curious, can you post a playpen example?

    srrrse at 2018-04-12 13:38:56

  22. Here it is in the playpen: https://play.rust-lang.org/?gist=4cc04defbebf06fa55a3074499d256ad&version=nightly

    The line to uncomment to trigger the compilation error is not mentioned in the error.

    Dylan Ede at 2018-04-12 14:06:17

  23. I've come up with a hacky wrapper around generators that implements a trait like the one I mentioned above, so that you can return local references from generators. Caveats are mentioned inline. Take this as a proof-of-concept and motivating use case for changing the Generator trait.

    https://play.rust-lang.org/?gist=74fe6554d3fa5503594bb48889caed49&version=nightly

    Dylan Ede at 2018-04-13 09:44:04

  24. One possible signature for the Generator trait that avoids the need for GATs is

    pub trait Generator<'a> {
        type Yield;
        type Return;
        unsafe fn resume(self: Pin<'a, Self>) -> GeneratorState<Self::Yield, Self::Return>;
    }
    

    Users of the trait would then typically refer to for<'a> Generator<'a>.

    Dylan Ede at 2018-04-13 14:03:53

  25. @dylanede given that GATs are already on the list to be implemented, I'd rather wait on them. It'd be very unfortunate to be locked into a more frustrating syntax which is inconsistent with Iterator.

    Clar Fon at 2018-04-15 19:22:12

  26. When suggesting Generator changes, please, do not forget about the fact that they can be used outside of async use-cases. Also I think it's worth to consider renaming Yield to Item for better compatibility with Iterator and potential future unification.

    Artyom Pavlov at 2018-04-20 11:49:46

  27. fwiw, yield is used in Python and Javascript for generators not related to async behavior.

    Daniel Nugent at 2018-04-28 21:44:18

  28. Has anyone looked into the experimental LLVM coroutine support? @Zoxc on IRC mentioned that the current IR generated is hard to optimize, and the situation may improve if we passed it using the native LLVM facilities.

    Tatsuyuki Ishi at 2018-05-06 02:27:23

  29. Last time I looked at it, LLVM coroutines were basically purpose-built for C++ coroutines, which default to allocating their state block. I'm not sure how easy it would be to get around that, and even without that there are layering problems.

    It would be nice to be able to optimize the code pre-state-machine-transformation without lifting all of LLVM into MIR, though. :)

    Russell Johnston at 2018-05-06 02:32:10

  30. I noticed that Generator is implemented for mutable references to generators. I think that this is unfortunate because it prevents any safe abstractions for the Generator trait.

    Consider this seemingly safe function:

    fn get_first_yield<T>(mut gen: impl Generator<Yield = T>) -> Option<T> {
        // We know `resume` wasn't called on `gen` before because the caller needs to
        // move `gen` to call this method. Since we now own `gen` and we won't move it
        // anymore, it is safe for us to call `resume`.
        match unsafe { gen.resume() } {
            GeneratorState::Yielded(value) => Some(value),
            GeneratorState::Complete(_) => None,
        }
        // `gen` gets dropped here, therefore we are sure it isn't moved in the future.
    }
    

    According to the documentation of the Generator trait this function should be sound. But it isn't because it can be called with mutable references to generators. Here is a full example on how this function fails: https://play.rust-lang.org/?gist=94d0081f2f4e8fd08e4e0b97989422f1&version=nightly&mode=debug&edition=2018

    I know that generators will eventually get a safe api but depending on how long this will take I think it would make sense to remove the implementation of Generator for mutable references to generators now. Once there is a safe api, this implementation isn't possible anyway.

    zroug at 2018-10-04 12:44:58

  31. @zroug https://github.com/rust-lang/rust/pull/54383 should allow creating a safe api for generators soon :tm: (currently doing a local build of that branch to see if it works, will update in a few hours once it completes), as you mention impl<G> Generator for &mut G where G: Generator will have to disappear and instead we'll get impl<G> Generator for Pin<&mut G> where G: Generator or similar.

    Nemo157 at 2018-10-04 12:56:20

  32. In the same vein, last time I checked generators with borrows across yield points didn't impl !Unpin. That seems like the prime example of an !Unpin type, or am I misunderstanding something?

    Jonas Platte at 2018-10-04 13:00:42

  33. I can confirm that the following definition of Generator works with the changes in #54383 (full playground of what I tested):

    trait Generator {
        type Yield;
        type Return;
    
        fn resume(self: Pin<&mut Self>) -> GeneratorState<Self::Yield, Self::Return>;
    }
    
    impl<G> Generator for Pin<G>
    where
        G: DerefMut,
        G::Target: Generator
    {
        type Yield = <<G as Deref>::Target as Generator>::Yield;
        type Return = <<G as Deref>::Target as Generator>::Return;
    
        fn resume(self: Pin<&mut Self>) -> GeneratorState<Self::Yield, Self::Return> {
            <G::Target as Generator>::resume(Pin::get_mut(self).as_mut())
        }
    }
    

    I'm going to have a look whether I can figure out the changes to make the MIR transform match this trait as well.

    Nemo157 at 2018-10-04 15:17:05

  34. Going to again point out what I mentioned earlier: rather than a single resume method, I honestly think that resume should return Option<Yield> and that there should be a separate await method that consumes self and returns Return.

    Clar Fon at 2018-10-04 15:23:40

  35. Why generator doesn't accept values on resume? It'd be great to have yield return value provided into resume.

    Zakarum at 2018-10-25 12:23:15

  36. @clarcharr you can implement such await with current API.

    Zakarum at 2018-10-25 12:24:27

  37. Any update on whether Generator is going to be changed to depend on GATs, as suggested by https://github.com/rust-lang/rust/issues/43122#issuecomment-380802830?

    The strange behaviour mentiond in https://github.com/rust-lang/rust/issues/43122#issuecomment-380817278 is still reproducible as well.

    Dylan Ede at 2018-10-26 12:58:13

  38. @omni-viral Both definitions of generators can be written in terms of each other. I was more stating that I feel a double-method approach is more ergonomic for consumers.

    Clar Fon at 2018-10-26 13:02:13

  39. @clarcharr how do you write the pinned version in terms of a version of Generator that consumes self? Once it's pinned you're not allowed to move it so there is no way to produce a value to pass into await.

    Nemo157 at 2018-10-26 13:03:45

  40. Oh, right.

    It's unfortunate we don't have a way to do a form of consume-and-drop with the Pin API.

    Clar Fon at 2018-10-26 13:07:18

  41. Why Generator trait cares about immovability of the implementer, while Iterator does not? I am afraid Generator development is too heavily influenced with async use-cases, which results in an unfortunate disregard towards potential synchronous uses.

    Artyom Pavlov at 2018-11-08 19:54:47

  42. I'm fairly certain that Iterator would get the same treatment if it could be changed in a non-breaking way.

    Clar Fon at 2018-11-08 21:14:21

  43. @newpavlov note that only static generators are immovable. If you look at the testcases in the linked PR (e.g. https://github.com/rust-lang/rust/pull/55704/files#diff-d78b6984fcee145621a3415c60978b88) there's no unsafety required to deal with a non-static generator anymore, you just have to wrap references in Pin::new() before calling resume.

    This also means you can have a fully safe generic Iterator implementation for a wrapped generator by requiring a Generator + Unpin (which can also be passed a Pin<Box<dyn Generator>> (or stack-pinned variant of) if you want to define an iterator via an immovable generator).

    I was recently wondering if adding an &mut variant of resume for Unpin generators would be useful, e.g.

         fn resume_unpinned(&mut self) -> GeneratorState<Self::Yield, Self::Return> where Self: Unpin {
            Pin::new(self).resume()
         }
    

    but I can't think of a name that seems short enough to be worth it.

    Nemo157 at 2018-11-08 22:29:10

  44. @newpavlov also, not supporting immovability drastically lowers the usefulness of generators for pretty much all usecases, e.g. an iterator-generator as simple as

    || {
        let items = [1, 2, 3];
        for &i in &items {
            yield i;
        }
    }
    

    runs afoul of error[E0626]: borrow may still be in use when generator yields.

    Nemo157 at 2018-11-08 23:14:43

  45. @Nemo157 I guess I will repeat the question (couldn't find an answer with a cursory search), but could you remind me why Generator can not be implemented only for pinned self-referential types? In other words your snippet will automatically pin the generator closure.

    Artyom Pavlov at 2018-11-09 12:59:18

  46. will automatically pin the generator closure

    but where will it be pinned? To construct efficient adaptors you need to be able to pass the generator around by value as you add layers on, only once you want to actually use it do you pin the top level and have that pinning apply to the entire structure at once.

    It could be automatically pinned to the heap via Pin<Box<_>> but then you have an indirection between each layer of adaptors.

    Nemo157 at 2018-11-09 13:22:53

  47. You can distinguish between safe and unsafe generators, you will pass around by value "unsafe" generator, it will not implement Generator trait, but could instead implement something like UnsafeGenerator with an unsafe resume method. And we will have impl<T: UnsafeGenerator> Generator for Pin<T> { .. }, thus to safely use the resulting generator you will have to pin it first, which can be done either automatically or manually.

    Artyom Pavlov at 2018-11-09 13:41:45

  48. With #55704 we can distinguish between potentially self-referential and guaranteed movable generators, they're named Generator and Generator + Unpin respectively. Neither of them require any unsafe code to interact with.

    Nemo157 at 2018-11-09 13:43:17

  49. My point is that I am looking forward to using Generators in a synchronous code and implementing it manually for custom structs, so baking Pin semantics into the trait seems suspicious to me.

    Artyom Pavlov at 2018-11-09 13:44:58

  50. It's trivial to opt-out of pinning and use Generator still, I think this is a key part of providing all the potential power of generators, then allowing more user-friendly abstractions to be built on top of them that might restrict that in some way. Since Iterator can't be adapted to support pinned values directly you just opt-out in the adaptation layer and force users to pin any self-referential generator they want to use with it:

    impl<G: Generator<Return = ()> + Unpin> Iterator for G {
        type Item = <G as Generator>::Yield;
    
        fn poll(&mut self) -> Option<Self::Item> {
            match Pin::new(self).resume() {
                GeneratorState::Yielded(item) => Some(item),
                GeneratorState::Complete(()) => None,
            }
        }
    }
    
    let gen = || { yield 5; yield 6; };
    for item in gen {
        println!("{}", item);
    }
    
    let gen = static || { yield 5; yield 6; };
    for item in Box::pinned(gen) {
        println!("{}", item);
    }
    

    Nemo157 at 2018-11-09 14:17:06

  51. So why use this approach instead of the UnsafeGenerator which I've proposed earlier? IIUC both are essentially equivalent to each other, but with yours users have to learn about Pin semantics even if do not work with self-referential structs.

    Artyom Pavlov at 2018-11-09 14:21:37

  52. I do agree that the burden of figuring out how to work with self-referential structs should be put on the creators of the self-referential structs, rather than in the API for something like Generator.

    For example, why is it that we can't just use &mut self as usual and pass in an &mut PinMut<'a_ T> instead? It seems silly, but it does get around having to put this weird API in the Generator trait.

    Clar Fon at 2018-11-09 23:47:02

  53. If your generator is Unpin, then all someone has to do to use it is to do Pin::new(generator). In return, people who have generators that are not Unpin can also use your code and abstractions on their generators. The Pin API was designed so that pointers to structs which are not self-referential can be easily be put in and out of a Pin. Having a separate UnsafeGenerator trait would force everyone to implement things twice, once for UnsafeGenerator and once for Generator.

    Yuri Kunde Schlesner at 2018-11-09 23:52:01

  54. Also, you keep implying that somehow self-referential generators are only relevant for async i/o use cases, but that's not at all true. Any generator that uses borrows to local variables (like this example in this thread) will need to be self-referential.

    Yuri Kunde Schlesner at 2018-11-09 23:54:00

  55. Having a separate UnsafeGenerator trait would force everyone to implement things twice, once for UnsafeGenerator and once for Generator.

    Why is that? I don't see why impl<T: UnsafeGenerator> Generator for Pin<T> { .. } wouldn't work.

    Artyom Pavlov at 2018-11-10 00:07:44

  56. @newpavlov Just to make it clear: Pin has absolutely nothing whatsoever to do with asynchronous vs synchronous.

    The reason for Pin is to allow for borrowing across yield points, which is necessary/useful for both asynchronous and synchronous code.

    Pin just means "you cannot move this value", which allows for self-referential references.

    If your struct does not need to pin anything, then a Pin<&mut Self> is the same as &mut Self (it uses DerefMut), so it is just as convenient to use.

    It is only when your struct needs to pin something that you need to deal with the complexity of Pin.

    In practice that means the only time you need to deal with Pin is if you are creating abstractions which wrap other Generators (like map, filter, etc.)

    But if you're creating standalone Generators then you don't need to deal with Pin (because it derefs to &mut Self).

    Why is that? I don't see why impl<T: UnsafeGenerator> Generator for Pin<T> { .. } wouldn't work.

    Let's suppose we did that. That means that now this code won't work:

    let unsafe_generator = || {
        let items = [1, 2, 3];
        for &i in &items {
            yield i;
        }
    };
    
    unsafe_generator.map(|x| ...)
    

    It doesn't work because the map method requires self to be a Generator, but unsafe_generator is an UnsafeGenerator.

    And your Generator impl requires UnsafeGenerator to be wrapped in Pin, so you would need to use this instead:

    let unsafe_generator = || {
        let items = [1, 2, 3];
        for &i in &items {
            yield i;
        }
    };
    
    let unsafe_generator = unsafe { Pin::new_unchecked(&mut unsafe_generator) };
    unsafe_generator.map(|x| ...)
    

    And now you must carefully ensure that unsafe_generator remains pinned, and is never moved. Hopefully you agree that this is much worse than the previous code.


    If instead we require Pin<&mut Self> for the resume method, that means we don't need to do any of that funky stuff, we can just pass unsafe_generator directly to map, and everything works smoothly. Unsafe generators can be treated exactly the same as safe generators!

    The difference with your system and the Pin<&mut Self> system is: where is the Pin created?

    With your system, you must manually create the Pin (such as when passing unsafe_generator to another API which expects a Generator).

    But with Pin<&mut Self> you don't need to create the Pin: it's created automatically for you.

    Pauan at 2018-11-10 05:42:03

  57. Has there been any discussions about avoiding more than one Box allocation when embedding an immovable generator inside another? That's basically what happens in C++ for optimization purpose, but in Rust we probably need that in the type system.

    Another optimization question is about safe-to-move references: particularly, dereferenced Vec references will not change even if the generator moves, but due to how the types work it still requires an immovable generator for now.

    Tatsuyuki Ishi at 2019-03-11 05:51:20

  58. No boxes are needed, see https://docs.rs/pin-utils/0.1.0-alpha.4/pin_utils/macro.pin_mut.html for stack pinning that works even for generators in generators.

    Nemo157 at 2019-03-11 06:10:11

  59. Hi to everybody,

    experimenting with the generators now and I need to store multiple generators into a Vec. This code works:

    #![feature(generators, generator_trait)]
    
    use std::ops::{Generator, GeneratorState};
    use std::pin::Pin;
    
    
    fn main() {
    
        let gen = Box::new(|| {
            yield 1;
            return "foo"
        });
    
        let mut vec = Vec::new();
    
        vec.push(gen);
    
        match Pin::new(vec[0].as_mut()).resume() {
            GeneratorState::Yielded(1) => {}
            _ => panic!("unexpected return from resume"),
        }
    
        match Pin::new(vec[0].as_mut()).resume() {
            GeneratorState::Complete("foo") => {}
            _ => panic!("unexpected return from resume"),
        }
    
    }
    

    However I will also need to be able to explicitly specify the vec type without using the inference, but this is where I fail. This code does not work:

    #![feature(generators, generator_trait)]
    
    use std::ops::{Generator, GeneratorState};
    use std::pin::Pin;
    
    
    fn main() {
    
        let gen = Box::new(|| {
            yield 1;
            return "foo"
        });
    
        //let mut vec = Vec::new();
        let mut vec:Vec<Box<Generator <Yield=i32, Return=&str>>> = Vec::new(); // what is the correct type?
    
        vec.push(gen);
    
        match Pin::new(vec[0].as_mut()).resume() {
            GeneratorState::Yielded(1) => {}
            _ => panic!("unexpected return from resume"),
        }
    
        match Pin::new(vec[0].as_mut()).resume() {
            GeneratorState::Complete("foo") => {}
            _ => panic!("unexpected return from resume"),
        }
    
    }
    

    I get:

    error[E0277]: the trait bound `dyn std::ops::Generator<Return = &str, Yield = i32>: std::marker::Unpin` is not satisfied
      --> src/main.rs:19:11
       |
    19 |     match Pin::new(vec[0].as_mut()).resume() {
       |           ^^^^^^^^ the trait `std::marker::Unpin` is not implemented for `dyn std::ops::Generator<Return = &str, Yield = i32>`
       |
       = note: required by `std::pin::Pin::<P>::new`
    

    Any idea what is the correct way to explicitly declare the vector?

    quantverse at 2019-05-02 10:39:16

  60. Use Vec<Box<dyn Generator<Yield = i32, Return = &'static str> + Unpin>>, Unpin is a marker trait you can add on to other trait bounds.

    Or use Vec<Pin<Box<dyn Generator<Yield = i32, Return = &'static str>>>> if you want to support self-referential generators as well.

    Nemo157 at 2019-05-02 10:44:15

  61. Wow, thanks a lot!

    quantverse at 2019-05-02 10:54:24

  62. Has anyone done any work with generator resume arguments? Is there an RFC for them?

    Lachlan Sneff at 2019-05-04 18:47:51

  63. Not that I know of. The original RFC mentioned that resume arguments could be added, but that's all.

    Tatsuyuki Ishi at 2019-05-05 01:34:04

  64. @lachlansneff My initial PR adding generator included resume arguments and that implementation is still in one of my branches. Adding generator resume arguments would be covered by the existing eRFC for generators.

    Zoxc at 2019-05-05 01:59:09

  65. Found this: https://internals.rust-lang.org/t/pre-rfc-generator-resume-args/10011

    quantverse at 2019-05-08 10:18:26

  66. I have read through the RFC and all the comments here. I may be missing this. However, it doesn't seem that the case of cancellation has been considered.

    Let's say that I want to collect the first million numbers in the Fibonacci sequence. I can write a generator that never returns and yields the next number in the sequence. Then I can call resume() until I have one million results. So far so good. However, this needs to use some kind of BigNum because the numbers are large. The BigNum implements Drop to free its memory buffer.

    After I've collected my first million BigNums, I stop resuming. What happens to the two BigNums that I'm internally keeping as state? Are they dropped? Do we get a memory leak?

    It seems like some sort of cancellation routine is needed here to explicitly end execution early. It probably also needs to be defined in the Generator trait otherwise independent types implementing Generator will not be well-defined.

    Nathaniel McCallum at 2019-07-15 19:44:08

  67. After I've collected my first million BigNums, I stop resuming. What happens to the two BigNums that I'm internally keeping as state? Are they dropped? Do we get a memory leak?

    They are stored as part of the generated impl Generator type, the transform also produces a generated Drop impl for the type, so when you drop the generator all of the current state is correctly dropped (simple demonstration playground).

    Nemo157 at 2019-07-15 20:15:30

  68. After I've collected my first million BigNums, I stop resuming. What happens to the two BigNums that I'm internally keeping as state? Are they dropped? Do we get a memory leak?

    Generators work similarly to closures: they are converted into a struct which contains all of the state for the generator. So the generator struct will contain the BigNums as fields.

    When the generator struct is dropped, it will automatically drop all of its fields, so there is no memory leak. Here is an example:

    https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=74878c5f222d4329fdf654f5d873d44d

    Pauan at 2019-07-15 20:17:34

  69. Should Generator depend on Drop then? Third-party implementors of Generator need either clear documentation on the need to handle cancellation semantics or a strict dependency on Drop to force them to do so. Possibly both.

    Nathaniel McCallum at 2019-07-15 20:21:37

  70. @npmccallum I'm not sure what you mean. Things will automatically be dropped even if you don't implement Drop.

    So unless you're intentionally trying to leak memory, it's very hard to leak memory by accident.

    Pauan at 2019-07-15 20:25:25

  71. @Pauan So if you create a generator using the closure-like syntax, the Rust compiler will create a new ephemeral type, implement Generator and Drop on it and create an instance of that type.

    The Rust compiler, however, is not the only party that can implement Generator and Drop on a type. For example, a number of stackfull coroutine crates do this. We might want to document that if you implement Generator you also need to implement Drop and handle cancellation.

    We probably shouldn't make Generator depend on Drop since this would make it awkward to implement Generator on types which don't need to free resources.

    Nathaniel McCallum at 2019-07-15 20:35:36

  72. Unless you're actually managing memory yourself you don't need to implement Drop at all- when you just have a BigNum field the compiler will generate "drop glue" to call BigNum::drop for you. This is exactly the same as a case like this:

    struct SomeType {
        some_data: Vec<i32>,
    }
    
    // SomeType doesn't implement Drop, but you don't see it leaking anything
    

    Russell Johnston at 2019-07-15 20:38:33

  73. @rpjohnst Agreed.

    Nathaniel McCallum at 2019-07-15 20:40:35

  74. So if you create a generator using the closure-like syntax, the Rust compiler will create a new ephemeral type, implement Generator and Drop on it and create an instance of that type.

    That is correct, yes.

    We might want to document that if you implement Generator you also need to implement Drop and handle cancellation.

    No, that is not necessary. What I said is not specific to generators, it applies to all Rust types:

    https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=f6d244f8fc13f887a99d1f3f425ddaec

    As you can see, even though Bar does not implement Drop, it still dropped Foo anyways. And even if Bar does implement Drop, it still drops Foo anyways:

    https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=1b401bfa0b63d97bd81a35f6a5529f84

    This is a fundamental property of Rust: everything is automatically dropped, even if you do not implement Drop. That applies to all Rust types (with a couple exceptions like ManuallyDrop).

    The only time you need to implement Drop is if you're managing some external resource (like the heap, or a file descriptor, or a network socket, or something like that). But that's not specific to generators.

    It's very hard to accidentally leak memory in Rust. The only way I know of to leak memory accidentally is with Rc / Arc cycles.

    Pauan at 2019-07-15 20:42:21

  75. To be fair, if you are implementing something acting like the coroutines created by the generator transform then you are likely to be manually managing your own internal memory to deal with the states efficiently e.g. translating my earlier example into a manually implemented Generator uses MaybeUninit everywhere and needs a custom Drop impl to take care of it (and is highly likely to not be panic safe).

    But still that is not specific to generators/coroutines, that's just to do with manually managed internal memory, and doesn't require any changes to this feature to deal with.

    Nemo157 at 2019-07-15 21:08:55

  76. While this is still unstable, can async/await be used in Rust stable in place of generators in a way that does not carry in the full fledged futures and task scheduler run-time, but rather in a more simply typed localized iterator-like interface?

    Dan Aloni at 2020-01-12 09:54:26

  77. Sure, there are even crates for that. See https://github.com/whatisaphone/genawaiter for example.

    Hadrien G. at 2020-01-12 10:01:28

  78. I compared a genawaiter Iterator with the async_std way of accomplishing the same task, i.e. an iterator async task send via a async_std::sync::Sender plus an receive Iterator that does async_std::task::block_on(receiver.recv()). The latter has needless calls to the futex syscall on Linux, and genawaiter of course does not, which is cool. Thanks @whatisaphone, @HadrienG2

    Dan Aloni at 2020-01-12 16:56:03

  79. Thanks for calling me out, I meant to leave a comment here. If anyone wants to experiment with generator control flow, using genawaiter as a starting point might be quicker than hacking the compiler itself.

    I think resume arguments are an important feature, and genawaiter supports them via the resume_with method. I couldn't come up with a good answer for where the first resume argument should end up (before the first yield statement). See the note in this section. I'd love to see a better way to handle that.

    John Simon at 2020-01-23 16:21:21

  80. @whatisaphone One design which was proposed before in order to resolve this problem is to unify generator call arguments and generator resume arguments. Basically, your generator acts like an FnMut(ResumeArgs).

    It is not very consensual yet though. Critics argue that it is surprising to have the arguments of a generator fn change magically every time you reach a yield statement, and that it's not nice to have to explicitely save them if you don't want to lose them.

    Hadrien G. at 2020-01-23 16:36:21

  81. @whatisaphone how about something like:

    // either of:
    // fn generator(initial_arg1: i32, initial_arg2: i64) -> String
    // yield(initial_resume_arg_pat: (i32, i32)) -> i32
    // {
    
    // or:
    fn generator(initial_arg1: i32, initial_arg2: i64) -> impl Generator<Return=String, Yield=i32, Resume=(i32, i32)>
    yield(initial_resume_arg_pat)
    {
        println!("{:?}", (initial_arg1, initial_arg2, initial_resume_arg_pat));
        let yield_value = 23i32;
        let next_resume_arg = yield yield_value;
        println!("{:?}", next_resume_arg);
        "return value".to_string()
    }
    
    fn main() {
        let mut g = generator(1i32, 2i64);
        let mut g = unsafe { Pin::new_unchecked(&mut g) };
        assert_eq!(g.resume((5i32, 6i32)), Yielded(23i32));
        assert_eq!(g.resume((7i32, 8i32)), Complete("return value".to_string()));
    }
    

    It prints:

    (1, 2, (5, 6))
    (7, 8)
    

    Generator lambda function:

    let lambda: impl FnOnce(i32, i64) -> impl Generator<Return=String, Yield=i32, Resume=R> =
    |initial_arg1: i32, initial_arg2: i64| -> i32 /* or impl Generator */
    yield(initial_resume_arg_pat: R) -> i32
    {
        todo!()
    };
    

    (edit: fix generator lambda type)

    Jacob Lifshay at 2020-01-23 17:44:40

  82. @HadrienG2 My idea avoids losing initial resume arguments and doesn't have magically changing variables.

    Jacob Lifshay at 2020-01-23 17:47:40

  83. I've implemented resume arguments in https://github.com/rust-lang/rust/pull/68524. They go for the "minimal language changes" solution of passing the first resume argument as a "normal" argument and subsequent ones as the result of yield expressions.

    I think we should land this implementation first, since it unlocks a lot of interesting patterns (including async/await support on #[no_std], which is what I'm interested in). Generators are experimental, so that shouldn't be a problem. Once we've collected some experience with this we can work on writing a fully-fledged RFC for generators.

    Jonas Schievink at 2020-01-24 21:52:02

  84. @jonas-schievink does your implementation of generator resume arguments relate to the RFC under discussion at https://github.com/rust-lang/rfcs/pull/2781 ?

    bstrie at 2020-01-28 19:10:34

  85. @bstrie No, I've found that proposal to be extremely confusing with how it changes the value of supposedly immutable variables after a yield. My implementation just makes yield evaluate to the resume argument, and passes the first one as an argument.

    Jonas Schievink at 2020-01-28 19:23:14

  86. I avoid using generators in Python because of their "obscure" syntax: to know whether you are looking at a functions definition or at a generator definition, you need to search for the yield keyword in the body. To me this is as if a function definition and a class definition in Python where both using def (instead of def and class), and to recognise a function, it was necessary to search for return keyword in the body.

    When i choose to define a generator in Python, i add a comment before the definition to mark it as a generator.

    I am surprised that Rust is following Python here. IMO, if both

    let mut one = || { if false {}; 1 };
    

    and

    let mut one = || { if false {yield 42}; 1 };
    

    are allowed, they should mean the same thing.

    I would expect the generator definition syntax to be remarkably different from the closure definition syntax.

    Alexey Muranov at 2020-01-31 10:35:08

  87. Yeah, I also quite dislike that generator syntax mimics closures, I have proposed an alternative syntax here, although it will need a new edition and probably we should use something else instead of generator, e.g. something like cort as a shorthand for coroutine (see this post for motivation).

    Artyom Pavlov at 2020-01-31 10:53:14

  88. @newpavlov @alexeymuranov I proposed a syntax that doesn't require a new edition but is unambiguous here. It also doesn't have magically changing function arguments.

    It's unambiguous because of the yield that is declaring the yield and resume types that is between the return type and the function body.

    A generator lambda function would look like:

    |initial_arg1: i32, initial_arg2: i64| -> i16 /* or impl Generator */
    yield(initial_resume_arg_pat: R) -> i32
    {
        let resume_arg2: R = yield 23i32;
        0i16
    }
    

    Jacob Lifshay at 2020-01-31 17:58:14

  89. I like how libfringe does it where the initial input (resume arguments) to the generator are specified as part of the closure:

    |input, yielder| {
      loop {
        input = yielder.yield(input + 1);
      }
    }
    

    Fabian Franz at 2020-08-02 12:05:16

  90. I raised this question on discord and was asked to add it here: Where is the high-performance cooperative multitasking facility?

    Clarifications: I'm writing a very high performance simulation system that is most comfortably modeled with actors (or as I knew it, communicating processes, CSP). You can easily do it with (OS) threads, but that's 3-4 orders of magnitude slower than doing it with coroutines from a single pinned thread.

    Benchmarking modern C++ stackless corutines I can get 600 M/s context switches (which is insanely good), roughly 12 x86 instructions per iteration. Closer to the model I want is cooperative multitasking, which the Boost Contexts provides. This "merely" manages ~ 100 M/s context switches.

    Alas doing to do this in Rust has been a disaster; I've tried some six different crates, but everything is tuned for multithreading and IO, not for compute bound single thread. Performance is typically ~ 300 k/s (eg. tokio). The best I have gotten so far is with futures_lite and spin_on which can eek out 50 M/s (and a ~ 64 instruction inner loop with lots of indirect branches). Looking at the binary trace it seems trying to shoehorn cooperative multitasking over async incurs a fair bit of overhead.

    Tommy Thorn at 2020-10-26 01:41:58

  91. I forgot to include my best case example that manages 50 M/s switches (1/6th of what I can do with C++):

    use futures_lite::future;
    
    pub fn main() {
        let my_future = async {
            for _step in 0..100_000_000 {
                future::yield_now().await;
            }
        };
    
        println!("size {}", std::mem::size_of_val(&my_future));
    
        spin_on::spin_on(my_future);
    }
    

    Tommy Thorn at 2020-10-26 01:43:22

  92. @tommythorn the perf difference is likely because the C++ version does not have/need the equivalent of a "waker" to notify a future when to resume.

    Since you are using spin_on, and your task is CPU bound and not I/O bound, it may be that you do not need this waker system at all.

    However, future::yield_now() will always notify the executor via the task's waker. You could probably improve performance by writing a custom future that returns NotReady the first time it is called, but does not notify the waker. Awaiting this custom future would be more efficient (although strictly speaking you would be violating the contract expected of futures).

    In the long run, I think Rust will get generalized coroutines, which would allow you to use the state-machine transform performed by the compiler, without being tied to the async/await model. I wrote down my thoughts on how this should look here.

    Diggory Blake at 2020-10-26 02:11:13

  93. Exactly what it looked like from reading the dynamic instruction trace (a dummy waker was consulted etc). I only have a few month of Rust experience, but I'll try. I'll not pollute this thread any longer, but I must plead that "generator/coroutines" is not what I need and implementing cooperative multitasking on top of generators is critical overhead. What I would like is actual coroutines as defined by Knuth (https://en.wikipedia.org/wiki/Coroutine) with explicit transfer of control, not Python style generators. Thanks.

    Tommy Thorn at 2020-10-26 02:20:27

  94. I posed once a question on Rust reddit: How about state machines instead of generators?. There may be some relevant comments there.

    Alexey Muranov at 2020-10-26 09:37:41

  95. I must plead that "generator/coroutines" is not what I need and implementing cooperative multitasking on top of generators is critical overhead. What I would like is actual coroutines as defined by Knuth (https://en.wikipedia.org/wiki/Coroutine) with explicit transfer of control, not Python style generators. Thanks.

    Did you read https://en.wikipedia.org/wiki/Coroutine#Comparison_with_generators ?

    There should be zero overhead to implementing coroutines on top of generators like this: in both cases you would need an indirect call as soon as more than one coroutine is involved.

    Diggory Blake at 2020-10-26 11:31:09

  96. @Diggsey, I didn't read that but it's a well know fact, but it's not free; you are implementing a trampoline and thus you transfer control twice, at least one of which is an indirect branch.

    In detail: in the classic coroutine implementation yield_to(B) while running A, will save callee registers, store the stack pointer in A, load the stack pointer from B, load the callees, and return. That's it. You have one call to the yield_to(B) and one return. With a generator, you have three contexts to switch between: A -> C -> B (looking at your link they correspond to A-produce, B-consume, and C-dispatcher. Twice the cost.

    ADD: Note, with a small change to yield_to() to allow it to pass a value to the next thread, eg: yield_to(B, value) which will arrive as the return value in B: value = yield_to(...), you can implement generators directly on top of this with zero cost. (I used exactly this primitive in a high performance firmware for a NAND flash controller. It was almost as fast as the convoluted state machine it replaced, but vastly more readable and less error prone).

    Tommy Thorn at 2020-10-26 20:23:54

  97. With a generator, you have three contexts to switch between: A -> C -> B

    I'm not sure what definition of "context switch" you are using, but this is definitely not true.

    With a generator (based on the state-machine transform), the call stack initially looks like this:

    A <-- top
    C
    

    When A yields, it corresponds to a ret:

    C <-- top
    

    And then C calls B:

    B <-- top
    C
    

    In other words, switching coroutines in this model involves a return and an indirect call. That's it. There's no stack swapping or other context switching happening at all.

    What you described here as a "classic coroutine implementation" is often slower as it requires swapping out the whole stack, ie. there's a real context switch happening. Furthermore, the compiler cannot optimize across the context switch. Using a state-machine transform is both more cache-friendly and more compiler friendly.

    Diggory Blake at 2020-10-26 21:53:08

  98. @tommythorn : Some of the confusion might be that while async/await use a trampoline, the coroutines/generators described by this issue are a lower level building block (for which there is no stable syntax: can see examples of the unstable API in https://github.com/rust-lang/rust/pull/68524/files?file-filters%5B%5D=.md&file-filters%5B%5D=.stderr) that work the way @Diggsey describes. The async/await transform consumes that low level building block to add a high level interface that is amenable to concurrent/multi-core schedulers.

    Stu Hood at 2020-10-26 23:05:01

  99. I have a use case for generators that isn't currently supported: I need a Generator that has access to a reference only for the short duration between yields.

    Here's an example:

    #![feature(generators)]
    
    use std::ops::Generator;
    
    fn test() -> impl Generator<&mut String> {
    // What lifetime goes here? ^
        
        |resume: &mut String| {
            resume.push_str("hello");
            let resume: &mut String = yield ();
            resume.push_str("world");
        }
    }
    

    This could be done using GATs if the Generator trait looked more like this:

    trait Generator {
        type Resume<'a>;
        type Yield;
        type Return;
        
        fn resume<'a>(self: Pin<&mut Self>, arg: Self::Resume<'a>) -> GeneratorState<Self::Yield, Self::Return>;
    }
    
    fn test() -> impl Generator<Resume<'a> = &'a mut String> {
    

    vultix at 2020-12-10 18:47:15

  100. See https://github.com/rust-lang/rust/issues/68923, the function signature there doesn't need GATs, if transient references actually worked then it could be:

    fn test() -> impl for<'a> Generator<&'a mut String>
    

    Nemo157 at 2020-12-11 09:54:23

  101. @tommythorn Is your c++ code publicly available? (regarding your stackless coroutine benchmark)

    cynecx at 2020-12-12 04:43:28

  102. This code is curtesy of Professor Jose Renau, reproduced with permission (attached). Compile with g++ -fcoroutines -O3 -std=c++2a sample.cpp

    sample.cpp.txt

    Tommy Thorn at 2020-12-12 07:33:10

  103. I did a cursory look but couldn't find an answer about this.

    Given that it is undefined (or maybe just unspecified) what resuming a generator that has returned. Why doesn't .resume(...) take an owned self and then return it by owned self in the yield enum varient?

    Or would this break the desire to be able to have trait objects of generators?

    Sebastian Malton at 2021-02-13 20:57:57

  104. @Nokel81 IIRC this approach was considered in the early stages, but unfortunately was discarded. I also would love for type system to ensure that it's impossible to call a returned generator (same with iterators), but having signature like this one:

    pub enum GeneratorState<G: Generator> {
        Yielded((G, G::Yield)),
        Complete(G::Return),
    }
    
    fn resume(self) -> GeneratorState<Self>;
    

    Would mean that on each yielding resume the generator state will be moved (read memcopied) to method and then back out of it. Compiler may optimize those moves out, but it's not guaranteed. At the very least we will need something like "optionally consuming methods" to help compiler with removing those moves (AFAIK there is no such proposal right now).

    Artyom Pavlov at 2021-02-13 21:39:08

  105. @newpavlov I thought that from the rust ABI point of view moving ownership around didn't necessarily mean that it had to actually move around.

    But yes a form of "consume and optionally return" that is guaranteed by the compiler would help.

    Sebastian Malton at 2021-02-13 21:53:41

  106. Generator traits are extremely helpful in developing asynchronous operating system kernels. We can regard user applications as generators, use resume to run them, and it will yield when any interrupt happens. When it yields, it will provide us with why the program has interrupted, fill into return value, thus the resume function returns. After processing the interrupt in executor, we may continue to execute it using resume.

    When kernels regard applications as generators. applications also regard kernels as generators. In asynchronous kernels we provide tasks other than threads in kernel. The application will resume to all its tasks, and when interrupt happens, applications will know it when interrupt returns, and switch to another task. That's just like a person draw a circle on Earth and stand in the circle, everyone else see this person is in the circle, and at the same time this person will see everyone else in the circle too.

    I have written code using concept of generator resume in my experimental kernel: executor and how to use it. Or pseudocode like this:

    let mut rt = executor::Runtime();
    rt.prepare_first_application();
    loop {
        match rt.resume() {
            ResumeResult::Syscall(ctx) => { /* process syscalls*/ }, 
            /* after that, next `resume` will resume current application */
            ResumeResult::IllegalInstruction(a) => { /* handle error and exit, */ rt.prepare_next_application(); }
        }   /* after that, next `resume()` will run next application */
    }
    
    fn resume(&mut self) -> ResumeResult { 
        asm!("save executor context", "load user context", "jump to user application")
    }
    fn interrupt() -> ! { 
        asm!("save user context", "load `interrupt_return` into return address", "jump to handle_interrupt")
    }
    fn handle_interrupt() -> ResumeResult { 
        return IsaSpecificCauseRegister::read().into()
    }
    fn interrupt_return() -> ! {
        asm!("load executor context", "jump to executor") 
        // it will return to function `resume`, and returns a `ResumeResult` from handle_interrupt
    }
    

    This might prove as an actual development scenario where Rust's generator will come into use.

    Luo Jia / Zhouqi Jiang at 2021-05-01 07:48:06

  107. Pinging, is there any update to its stablization?

    Jiahao XU at 2022-07-13 03:27:09

  108. I thought I'd chip in another use-case for transient references, just so that it's surfaced somewhere: Efficient streaming parsers.

    With that I could take my (probably not very efficient) manual state machine code

    /// [1]
    fn document<'a>(buffer: &mut StrBuf<'a>, state: u8, ret_val: RetVal) -> NextFnR<'a> {
    	match (state, ret_val) {
    		(0, _) => Call(1, prolog),
    		(1, Success) => Call(2, element),
    		(2 | 3, Success) => Call(3, Misc),
    		(3, Failure) => Exit(Success),
    		(1, Failure) => Error(Error::Expected22Prolog),
    		(2, Failure) => Error(Error::Expected39Element),
    		_ => unreachable!(),
    	}
    	.pipe(Ok)
    }
    

    and write

    fn document() -> impl for<'a> Generator<&mut StrBuf<'a>, Yield = Result<Event<'a>, NeedsMoreData>> {
      |buffer| {
        yield_all!(prolog())?; // Has `Return = Result<(), _>` as it downgrades if not present.
        if !yield_all!(element())? {
          Err(Error::Expected39Element)
        } else {
          while yield_all!(Misc())? { /* loop */ }
          Ok(true)
        }
      }
    }
    

    instead without too much macro weirdness.

    (I just noticed the next-gen crate, that's probably worth a try for the time being… Edit: No, I don't think it supports transient borrow.)

    Tamme Schichler at 2022-10-09 08:03:36

  109. As far as converting manual state machines to generators, I really like the propane crate

    Propane is a thin wrapper around the unstable generator feature, allowing users to create free functions as generators.

    Cameron Elliott at 2022-12-12 21:32:29

  110. Since GAT is already stablised, I thin perhaps the Generator should also be changed to support GAT?

    pub trait Generator<R = ()> {
        type Yield<'a> where Self: 'a;
        type Return;
    
        fn resume<'a>(self: Pin<&'a mut Self>, resume: R) -> GeneratorState<Self::Yield<'a>, Self::Return>;
    }
    

    Jiahao XU at 2023-01-28 01:23:59

  111. I am wondering if we can add an IntoGenerator trait, just like the IntoIterator trait and the IntoFuture trait, maybe something like:

    pub trait IntoGenerator<R = ()> {
        type Yield;
        type Return;
        type IntoGenerator: Generator<R, Yield = Self::Yield, Return = Self::Return>;
    
        fn into_generator(self) -> Self::IntoGenerator;
    }
    

    Then, for a generator function, instead of returning an object that implements Generator, we can return an object that implements IntoGenerator. The reason is that a Generator type may have a rather large size, depending on the logic of the function body. But a IntoGenerator type only need to include the information of the function arguments, regardless of the logic of the function body, which could be smaller than the corresponding Generator object.

    Consider a case where I need to send a generator object through a channel into another thread for execution. Instead of sending a Generator object, I can send a IntoGenerator object, which could be faster, since there are less bytes to copy. An extreme case is that a generator function that have no argument can produce a IntoGenerator object that have size of zero.

    EFanZh at 2023-01-28 12:34:17

  112. IntoGenerator as a trait also makes sense from the perspective of channels and other objects which require setup before they can start generating; IntoGenerator would perform the setup, then Drop would perform the teardown. That way, you can pass in a value to functions knowing that it can be started in a generic way, but also don't start it until needed.

    Clar Fon at 2023-01-29 20:38:19

  113. Since GAT is already stablised, I thin perhaps the Generator should also be changed to support GAT?

    pub trait Generator<R = ()> {
        type Yield<'a> where Self: 'a;
        type Return;
    
        fn resume<'a>(self: Pin<&'a mut Self>, resume: R) -> GeneratorState<Self::Yield<'a>, Self::Return>;
    }
    

    I really hope this gets added, I'd like to avoid the situation we currently have for lending iterators.

    Julia at 2023-06-15 17:33:59

  114. So, the description of this issue just links issues tagged as A-generators, but what is actually required at the moment to stabilise this?

    I once again found myself wishing that I could create iterators using generators like Python or JS, and was wondering what the minimum path forward is. I know that generators are already being used a lot implicitly by async functions, but it would be lovely to be able to use them in other cases as well.

    Clar Fon at 2023-10-02 15:01:22

  115. The next step is an actual RFC, eRFC2033 only proposed adding generators as an unstable implementation detail of async (I believe the only reason it went through the RFC process is because it's quite a large surface for an implementation detail).

    Nemo157 at 2023-10-03 21:43:31

  116. I see. Is there a WG that's in charge of that, or is this mostly just up for grabs?

    Since I feel like a generators WG would make sense for this since it has so much surface area. What feels like the right path to me is effectively moving to replicate most of the iterator API on generators, then just auto-implementing Iterator for generators that are Unpin and have no return value. Which in itself is a lot of work.

    One particular immediate issue I see is that generators actually have a natural way of distinguishing infinite iterators from finite ones (infinite iterators return !, finite iterators return ()) and the same things that are blocking ! stabilisation are probably blocking us from implementing Iterator for both of those kinds of generators too.

    Clar Fon at 2023-10-04 04:46:51

  117. On my wish-list is for the resume type to be a GAT rather than a type parameter (I'm not even sure why it's a type parameter right now? I don't see any value in a type implementing Generator multiple times with distinct resume types...), allowing us to pass resume values with distinct lifetimes back into the generator. For example:

    pub trait Generator {
        type Yield;
        type Return;
        type Resume<'a>;
    
        fn resume<'a>(self: Pin<&mut Self>, arg: Self::Resume<'a>) -> GeneratorState<Self::Yield, Self::Return>;
    }
    

    On the generator side, the value you get out of an invocation of yield(_) would be a value that lives until the next yield. For example:

    let resumed_value = yield(1);
    // <-- Legal to use `resumed_value` here
    let resumed_value2 = yield(2);
    // <-- Illegal to use `resumed_value` here, the second `yield` invalidates the first resume value
    

    This seems of questionable value, except for the fact that this design allows you to repeatedly pass some sort of long-lived state/context into a generator for it to operate upon. For example:

    struct Log(Vec<String>);
    
    // (I realise that `for<'a> &'a mut Log` isn't valid syntax since Rust doesn't support lifetime HRTs yet)
    fn generator_that_logs() -> impl Generator<Resume = for<'a> &'a mut Log, Yield = i32> {
        let log = yield(1);
        log.0.push("Just generated '1'".into());
        let log = yield(2);
        log.0.push("Just generated '2'".into());
    }
    
    // Example usage
    
    let mut gen = std::pin::pin!(generator_that_logs());
    
    let mut log = Log(Vec::new());
    assert_eq!(gen.resume(&mut log), GeneratorState::Yielded(1));
    assert_eq!(gen.resume(&mut log), GeneratorState::Yielded(2));
    
    assert_eq!(log.0, vec![
        "Just generated '1'".into(),
        "Just generated '2'".into(),
    ]);
    

    Being able to communicate with a generator using some extra context where said context has a lifetime bound to the caller of resume makes generators immensely more useful for many applications, particularly those with deferred execution patterns that need to do some useful work on the context between invocations of resume. The only ways to do this today require a lot of unsafe (which can't be hidden from the user) and have extremely questionable soundness.

    Joshua Barretto at 2023-10-15 20:06:26

  118. Hey, I used to having core::iter::from_generator in a project, but after upgrading the compiler version, it tells me that:

    unknown feature iter_from_generator

    What should I do now, to get an iterator from a generator?

    Félix at 2023-10-21 18:21:15

  119. Generator were renamed to coroutines recently (https://github.com/rust-lang/rust/pull/116958). The feature is now called iter_from_coroutine.

    Jakub Beránek at 2023-10-21 18:23:24

  120. Generator were renamed to coroutines recently (#116958). The feature is now called iter_from_coroutine.

    What a quick answer! Thanks a lot. Does it change anything to the actual feature? Can I write an iterator directly with a generator syntax, or must I still use core::iter::from_coroutine + closure coroutine/generator?

    Félix at 2023-10-21 18:28:34

  121. I have no idea, but I think that this PR just renamed stuff, and didn't change any functionality.

    Jakub Beránek at 2023-10-21 18:38:51

  122. On my wish-list is for the resume type to be a GAT rather than a type parameter (I'm not even sure why it's a type parameter right now? I don't see any value in a type implementing Generator multiple times with distinct resume types...), allowing us to pass resume values with distinct lifetimes back into the generator.

    ~I can't agree enough. There's no excuse to add yet another core trait that's been handicapped due to the lack of GATs. While we're at it,~ Yield should also be a GAT. For example, that would enable coroutines to be used for LendingIterator (as Iterator is one of those aforementioned handicapped traits) implementations.

    EDIT: It's unnecessary for the resume type to be a GAT, as long as you can use for<...> as in the issue @Nemo157 brought up, but it's crucial that Yield be a GAT so it can mention the lifetime on self.

    initial-algebra at 2023-11-13 18:38:09

  123. I started experimenting with coroutines and it looks like they implement lifetimes incorrectly? https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=a7a1af3f2e2259aaa7df29350763e27f

    I would expect it to impl<'a, 'b> Coroutine<&'a mut &'b [u8], Yield=Result<(), ()>, Return=Result<(), ()>> for {coroutine@src/main.rs:14:27: 14:46} but it seems to use some specific lifetime (how's that even possible?!) instead of all lifetimes.

    Martin Habovštiak at 2023-11-14 19:31:34

  124. @Kixunil https://github.com/rust-lang/rust/issues/68923

    Nemo157 at 2023-11-14 19:46:04

  125. One thing I'd like to see implemented is a way to strongly type yield and return types together.

    enum Yield {
      Foo,
      Bar,
    }
    
    enum Resume {
      FooResponse,
      BarResponse,
    }
    
    type BadState<C> = CoroutineState<C, Yield, Resume>;
    

    yield Yield::Foo could be resumed with Resume::BarResponse, which is pretty bad! Maybe resuming a coroutine could return a new coroutine with the same state but different Yield/Return types.

    tezlm at 2024-02-29 22:39:01

  126. We recently did some exploratory work to find out if Rust could support our streaming architecture - and hit several of the roadblocks mentioned in this thread.

    Thus, I am here to toss in our two cents.

    At a very high level generators, coroutines and streams should be somewhat complimentary concepts, regardless of language. Especially I mean that a calling scope loops/iterates/traverses until end of some incoming output, seamlessly forwarding underlying yields.

    foo() Scope

    • Single yield "Header: \n"
    • nested loop over foo_content()
      • yield some_content
      • yield bar(some_content)
        • various operations..
        • yield some_processed_content
        • yield some_optional_content

    Ultimately the yield of some_processed_content and some_optional_content should end up becoming the yield of upstream functions when the developer says so. One could think of the total call stack as a tree and each yield as a node on such a tree.

    Then, resume/pause/etc are simply a form of iterating cursor, keeping up with what has been traversed so far.

    Cheers

    SuitespaceDev at 2024-03-08 15:43:26

  127. I made a simple test of coroutines for my use case, but there seems to be a problem with lifetimes of values passed into resume(): https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=0b35977241bf770cb2a782e2360f0dcf

    Isn't borrow supposed to end after the call?

    Anton Smetanin at 2024-04-01 12:36:15

  128. @antonsmetanin it's even worse: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=491f8437083e51ec5c0bf3aedf7e5d04; the values passed into are used through all generator invocations.

    Ellen Emilia Anna Zscheile at 2024-04-01 13:47:14

  129. There's an existing issue about giving coroutines the ability to borrow the argument to resume: rust-lang/rust#68923. And also some recent discussion that happened on a different issue: https://github.com/rust-lang/rfcs/pull/3513#issuecomment-2007737795

    Russell Johnston at 2024-04-01 16:04:28

  130. Today I had a go at using coroutines to write a high-performance VM that supports reentrant code (effect handlers and the like). The idea is to produce a chain of TCO-ed dynamic calls like threaded code, except with each instruction being a little coroutine instead of just a regular closure.

    It's so close to working very well and producing near-optimal code (something I was very shocked at), but unfortunately the codegen is being nuked from high by the panic-if-resumed-after-return check, resulting in extra branches and stack spilling.

    I know that this is way outside the bounds of what most people are probably going to be using coroutines for, but I'm very much wishing for an unsafe version of Coroutine::resume, perhaps called Coroutine::resume_unchecked, that elides the check, with the consequence that calling the method after the coroutine has returned is UB. Having this really would make coroutines zero-cost in the most literal sense of the phrase.

    Joshua Barretto at 2024-04-06 22:17:14

  131. It's sad we can't impl<C: Coroutine<Return = ()> + Unpin> IntoIterator for C just because it might overlap with impl<I: Iterator> IntoIterator for I.

    In Python it's easy to define a "yielding" generator function that actually returns an iterator when called:

    def yield1to3():
        yield 1
        yield 2
        yield 3
    

    In Rust,

    #[coroutine] || {
        yield 1;
        yield 2;
        yield 3;
    }
    

    is not a closure, it's the coroutine (generator) itself, but it can't be used as an iterator without explicit conversion.

    Sunshine40 at 2024-06-20 12:59:56

  132. @Sunshine40 You'd want actual generators:

    #![feature(gen_blocks)]
    
    fn main() {
        for v in gen { yield 1; yield 2 } {
            println!("{v}");
        }
    }
    

    https://play.rust-lang.org/?version=nightly&mode=debug&edition=2024&gist=d86514b466bd8051a17f2fdea5bbe67d

    cynecx at 2024-06-20 13:16:51

  133. @zesterer for a while I was thinking of writing an article about this for Future but I thought it's a micro-optimization, so I've been putting it off. If your experience proves otherwise I might actually try doing it sooner. Just note that for perfect zero-cost just having resume_unchecked is not enough because the mere presence of resume necessitates storing some flag to know whether the coroutine (future) panicked. It needs a completely new trait and code that handles it which I believe can be added backwards-compatibly. (impl<T: Coroutine> UnafeCoroutine for T, then bounding by UnsafeCoroutine and having a helper that fuses UnsafeCoroutine into Coroutine for those cases where you have to.) It also needs an additional method that signals to the coroutine you're giving up and dropping it before it was finished.

    Martin Habovštiak at 2024-06-22 09:52:03

  134. Fascinated by the conversation at large but especially between @zesterer and @Kixunil here I'm very familiar with coroutines in general but also quire n00b at Rust so please forgive if this ends up being nonsensical haha.

    That said; I wonder if it could be handled by the mere behavior of Coroutine or alternatively, instead of making an unsafe version, there is a RepeatingCoroutine or looping or some such.

    i.e., is it actually required that Coroutine panic on resume if trying to run after return just yields () or a zero-size struct? Alternatively, or perhaps following such a yield, resetting the iteration. Then the upstream function could decide what to do and a panic-capable coroutine could still be derived from it.

    I am mixing logic from different languages here so, again, perhaps this makes no sense to however Coroutines are being implemented. Glad to see the progress so far from userland though. thx

    Approach at 2024-06-23 09:36:26

  135. i.e., is it actually required that Coroutine panic on resume if trying to run after return just yields () or a zero-size struct?

    That would change the return type, now all of them would be injected with an Option or ()

    Alternatively, or perhaps following such a yield, resetting the iteration.

    It's only possible when the coroutine is pure (no I/O), and the parameters for it are either only used by immutable reference, or copyable/clonable.

    And it has performance implications: These parameters have to be saved somewhere, and copying/cloning isn't free.

    Then the upstream function could decide what to do and a panic-capable coroutine could still be derived from it.

    IMO it is possible that rust has a FusedCoroutine, similar to FuseFuture, which always return a certain variant (e.g. yield in coroutine, Pending in FusedFuture) and has a method to test if it has returned.

    Jiahao XU at 2024-06-23 09:49:10

  136. @NobodyXu ah yes that makes perfect sense of course. I suppose the hope was that the Rust internals had some magic type wrangling to hide what my mind wants to think of as "forwarding null". I see that would not be the case.

    Per resetting, that does make sense as well. Does that then imply that, when pure, @zesterer 's UnsafeCoroutine could be both trivial and safe?

    Per upstream handling of safety, very nice to hear that. From the sound of it FusedCoroutine would achieve what I was intending. thx

    Approach at 2024-06-23 13:30:21

  137. Per resetting, that does make sense as well. Does that then imply that, when pure, @zesterer 's UnsafeCoroutine could be both trivial and safe?

    I think adding a new unsafe method is definitely doable and achievable, though ideally compiler should optimize out the panic landing code.

    Jiahao XU at 2024-06-23 14:50:51

  138. Does that then imply that, when pure, @zesterer 's UnsafeCoroutine could be both trivial and safe?

    The reason for unsafe is actually Rust's moves. If you want to yield a network connection from a coroutine you can only do that once, so you either have to keep track of it at runtime or compile time. Runtime is slower and the compiler currently can't check it without introducing other costs (e.g. moving stuff around a lot), so only unsafe remains for performance-critical things.

    There are tricks to deal with the unsafety though. The code is generated by the compiler, so the implementation doesn't need unsafe and calling it us usually done by some kind of executor which can be an easy place to audit unsafe. Further if we get &move references (AKA stack box - basically Box that has a lifetime because it's backed by stack; main feature being the ability to move out of it without runtime checks) we can design a safe wrapper with fn resume(&move self) -> CoroutineState<(Y, &move Self), R> which enforces correctness with tiny additional cost (having to move the pointer out). It should still be cheaper overall than forcing all coroutines to handle the state because it doesn't multiply over multiple layers of coroutines (this may be more visible in async code where people call async functions from async functions, each call creating an additonal layer with all the panic handling).

    @NobodyXu the compiler can't optimize it across dynamic dispatch but a special trait can. I'm not suggesting that the cost of dynamic dispatch is small enough for this to matter, just stating the fact.

    Martin Habovštiak at 2024-06-24 07:00:39

  139. fn resume(&move self) -> CoroutineState<(Y, &move Self), R>

    I wish Future::poll also uses &move self to avoid panic landing.

    the compiler can't optimize it across dynamic dispatch but a special trait can.

    That's true, adding an unsafe method or use &mut self could avoid the panic overhead.

    Or maybe using a .fused() could avoid these panic overhead, at the cost of using Option<C> in layout and one additional if, it's definitely cheaper than panic.

    Jiahao XU at 2024-06-24 07:56:36

  140. @NobodyXu note that I've since realized that &move self is unsound because of the pin requirement to run destructor before overwriting the value and you can leak &move T which would prevent running the destructor. What is actually needed is PartialMove<Pin<&mut Self>> where PartialMove is a smart pointer that calls a special method (I chose the name cancel) which drops the parts that can be moved out but keeps whatever cannot, so that the pinned destructor can still run.

    Also I've found a way to return a zero-sized token instead of PartialMove<Pin<&mut Self>> so that the whole trait could be safe and zero-cost! It just needs a different signature. However I came up with all these things when designing a state machine that's neither Future nor Coroutine but it's sufficiently similar that the same ideas can be applied. It's just that I have to rewrite it in terms of Future to explain it. (I've already started writing down the previously mentioned things about Future just haven't finished.)

    Or maybe using a .fused() could avoid these panic overhead, at the cost of using Option<C> in layout and one additional if, it's definitely cheaper than panic.

    The performance cost is the same - it's one branch and one byte in memory. Less panics probably means smaller binary but hardly anyone cares about it.

    Martin Habovštiak at 2024-07-15 19:42:13

  141. Not sure if this is an appropriate place to put this, but I'd like to show a real-world example of coroutines being used in rust game dev. They're perfect for single threaded game engines.

    Here is an example of a very basic game engine that allows players (or any other world entity) to schedule coroutines for all sorts of various tasks. In this example we use a coroutine to show how we can make the player do an animation and lock their movement and yield until the animation is done before unlocking the players movement.

    I'm still quite new to rust but I'd absolutely love to see coroutines stabilized. They're not always needed but when they are needed they're incredibly useful.

    #![feature(coroutines, coroutine_trait, stmt_expr_attributes)]
    
    use std::collections::{HashMap, VecDeque};
    use std::ops::{Coroutine, CoroutineState};
    use std::pin::Pin;
    use std::time::{Duration, SystemTime, UNIX_EPOCH};
    
    fn main() {
        let mut world = GameWorld::default();
    
        let player_id = 1337;
    
        // Create a player
        world.add_player(player_id, String::from("Player1"), 0.0, 0.0);
    
        // Start task
        world.start_player_task(
            player_id,
            Box::pin(
                #[coroutine]
                |mut player: &'static mut Player| {
                    // Make our player do an animation
                    let anim = player.do_animation(100);
    
                    // Lock the players movement and
                    player.lock_movement();
    
                    // Yield until the animation is complete
                    player = yield anim.duration;
    
                    // Unlock movement now that our animation is complete
                    player.unlock_movement();
                },
            ),
        );
    
        // Start game engine
        loop {
            world.tick();
    
            // Wait for next tick
            std::thread::sleep(Duration::from_millis(600));
        }
    }
    
    type PlayerScript = Pin<Box<dyn Coroutine<&'static mut Player, Yield = f32, Return = ()>>>;
    
    pub struct Animation {
        pub id: i32,
        pub duration: f32,
    }
    
    pub struct PlayerTask {
        owner_id: i32,
        suspended_until: i128,
        coroutine: PlayerScript,
    }
    
    impl PlayerTask {
        fn is_suspended(&self) -> bool {
            self.suspended_until > 0 && current_time_millis() < self.suspended_until
        }
    }
    
    pub struct Player {
        pub id: i32,
        pub name: String,
        pub x: f32,
        pub y: f32,
        pub current_animation: i32
    }
    
    impl Player {
        pub fn do_animation(&mut self, animation_id: i32) -> Animation {
            self.current_animation = animation_id;
    
            // TODO load animation config by Id
            Animation {
                id: animation_id,
                duration: 3.2,// Hardcoded for now
            }
        }
    
        pub fn lock_movement(&self) {}
    
        pub(crate) fn unlock_movement(&self) {}
    }
    
    #[derive(Default)]
    pub struct GameWorld {
        players: HashMap<i32, Player>,
        tasks: VecDeque<PlayerTask>,
    }
    
    impl GameWorld {
        pub fn add_player(&mut self, id: i32, name: String, x: f32, y: f32) {
            self.players.insert(id, Player { id, name, x, y, current_animation: -1 });
        }
    
        pub fn start_player_task(&mut self, player_key: i32, script: PlayerScript) {
            let task = PlayerTask {
                owner_id: player_key,
                suspended_until: -1,
                coroutine: script,
            };
            self.tasks.push_back(task)
        }
    
        pub fn abort_task(&mut self, player_key: i32) {
            self.tasks.retain(|task| task.owner_id != player_key);
        }
    
        pub fn tick(&mut self) {
            let (tasks, players) = (&mut self.tasks, &mut self.players);
    
            println!("Tick players={} tasks={}", players.len(), tasks.len());
    
            tasks.retain_mut(|task| {
                if task.is_suspended() {
                    true // Nothing to do, task is suspended. Retain it, we're executing it later
                } else if let Some(player) = players.get_mut(&task.owner_id) {
                    println!("Executing task for player id={:?}", task.owner_id);
    
                    // Pin it
                    let pin: Pin<&mut PlayerScript> = Pin::new(&mut task.coroutine);
    
                    match unsafe {
                        // Since we know and control EXACTLY how these coroutines are executed, this isn't actually unsafe.
                        // Why do we know this? For the following reasons
                        // 1. Our engine is single threaded. We execute tasks one at a time and only once we have full ownership of the task owner.
                        // 2. Tasks will never execute when the owner doesn't exist
                        // 3. Coroutines only exist in 2 states.
                        //      1. Completed (the borrow is dropped anyways)
                        //      2. Yielded (the borrow is still active but we can guarantee its not in use since the coroutine is not doing anything in a yielded state)
                        //
                        // Having said all that, I think this should be functional and *safe* in theory.
                        let r: &'static mut _ = &mut *(player as *mut _);
                        pin.resume(r)
                    } {
                        CoroutineState::Yielded(seconds) => {
                            println!("yielded for {:?} seconds", seconds);
                            if seconds > 0.0 {
                                task.suspended_until =
                                    current_time_millis() + (seconds * 1000.0) as i128;
                            }
    
                            true // We must retain this task as its scheduled to execute later
                        }
                        CoroutineState::Complete(()) => {
                            println!("Coroutine has completed execution, removing task.");
                            false // Remove it, we're done
                        },
                    }
                } else {
                    // Player no longer exists, abort task and remove from list
                    println!("Player doesnt exist {:?}. Removing task.", task.owner_id);
                    false // Remove it. There's nothing to execute without a context, and things will get nasty
                }
            });
        }
    }
    
    fn current_time_millis() -> i128 {
        SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_millis() as i128
    }
    

    Jonathan at 2024-09-18 20:02:13

  142. Hi @jonatino,

    Your example program seemingly does not exhibit undefined behavior as written, since the implementation of the one instance of the coroutine carefully re-assigns player at each yield boundary. However, your implementation is certainly unsound, since it is possible to invoke undefined behavior by only changing the safe implementation code of the coroutine. The justification you've written for your unsafe lifetime extension is not valid; this is easily proven by holding on to a player reference past a yield boundary, which should be fine, and realizing that the reference is now pointing somewhere random on the stack where the player variable was last time the coroutine was resumed; thus it is invalid and you've invoked undefined behavior.

    I do think coroutines can be useful in game development, in similar contexts but with a different implementation.

    That said, I would suggest that you learn the full capabilities of the language as it exists today, as there are other mechanisms that you can use to accomplish the same goal in a different way without requiring the stabilization of any features.

    Gray Olson at 2024-09-18 23:51:38

  143. @jonatino https://github.com/rust-lang/rust/issues/68923 I don't know why your problem shouldnt be a fit for Coroutines, except that the lifetimes aren't sorted, yet. To my knowledge there is no mechanism in stable which encapsulates a state machine that elegantly.

    VVishion at 2024-09-30 21:27:26

  144. will Coroutine trait support GAT?

    currently

    let iter = #[coroutine]
        static || {
            let v = vec![1, 2, 3];
    
            for i in &v {
                yield i;
            }
        };
    

    is not work because

    yields a value referencing data owned by the current function

    Sherlock Holo at 2025-01-22 10:18:57

  145. Consider supporting yield-once coroutines, which allow you to expose real references to ephemeral “parts,” like slices of arrays, not to mention completely synthetic parts that reference no persistent memory. Getting rid of proxy references would be a huge win for generic programming.

    Dave Abrahams at 2025-02-02 20:25:54

  146. @dabrahams Could you give a more concrete example (e.g. pseudo-rust code)?, because I don't think Rust has an equivalent to Swift' Accessors, and I can't imagine a scenario where that might be useful (except perhaps as a "strict" subclass of futures)...

    Ellen Emilia Anna Zscheile at 2025-02-03 11:47:45

  147. @fogti IIUC that change, if you have a way to ensure the generator is resumed after yielding, would give Rust the equivalent of Swift accessors, the lack of which creates numerous ergonomic problems. I'll try to come up with an example using your feature.

    Edit: having looked more closely, I think the closure literal syntax may not be compatible with this use case. My Rust-fu is too weak to write even passable Rust pseudocode for you, unfortunately. I'll ask someone I'm working with to see if they can help.

    Dave Abrahams at 2025-02-03 18:59:31

  148. @dabrahams can you confirm if this example works for the context:

    Consider this (pseudo-)rust code where proxy references might be necessary without yield once coroutine:

    pub struct MappedArray<In, Out, F, FInv>
    where
        F: Fn(&In) -> Out,
        FInv: Fn(Out) -> In,
    {
        pub data: Vec<In>,
        pub f: F,
        pub f_inv: FInv,
    }
    
    impl<In, Out, F, FInv> MappedArray<In, Out, F, FInv>
    where
        F: Fn(&In) -> Out,
        FInv: Fn(Out) -> In,
    {
        pub fn at_mut(&mut self, i: usize) -> yield_once &mut Out {
            let mut mapped_val = (self.f)(&self.data[i]);
            yield (&mut mapped_val); // assuming mapped_val would be mutated to desired value by caller
            self.data[i] = (self.f_inv)(mapped_val);
        }
    
        pub fn push(&mut self, e: Out) {
            self.data.push((self.f_inv)(e));
        }
    }
    
    fn main() {
        let mut arr = MappedArray {
            data: Vec::default(),
            f: |x| x + 1,
            f_inv: |x| x - 1,
        };
        arr.push(2);
        arr.at_mut(0) = 3;
    }
    

    In this example, the correctness totally depends on the fact that control must come back to coroutine (most probably after last use of yielded value). For providing similar at_mut method for this class without yield once coroutine, proxy references would be necessary. A very similar usecase can be yielding a slice of array.

    With proposed syntax, I don't think we can yield a reference to support the above usecase, also I tried similar code and realized, it is not mandatory that control returns to coroutine after yielding at all.

    Rishabh Dwivedi at 2025-02-04 19:36:47

  149. for "control needs to return to coroutine after yielding" I think using callbacks/closures (e.g. as parameter to at_mut above) which gets passed the yielded values (or for yield once, just the value as-is) is usual, and I'm not sure what use case the above would enable which isn't possible using callbacks currently, and has "control needs to return to coroutine after yielding" behavior (e.g. as an opt-in option at the definition of the coroutine (here: at_mut))

    Ellen Emilia Anna Zscheile at 2025-02-04 20:20:39

  150. @fogti, while passing closures works in this context, it does impact the ergonomics of the API. It introduces unnecessary nesting on the calling side, which can be cumbersome. With coroutine syntax, callers can still think sequentially, making the code more readable and maintainable.

    Furthermore, yielding references with coroutines will not lead to lifetime problems, as it is guaranteed that control returns to the coroutine, similar to the behavior when using closures. So, complex borrow checking might not be necessary.

    If I am correct, improving ergonomics was one of the main reasons why modify and read accessors were introduced in Swift, as constantly passing closures can be cumbersome. @dabrahams can provide more insights on this.

    Rishabh Dwivedi at 2025-02-04 20:53:24

  151. @RishabhRD you might be onto something. If the call to "return reference" gets injected by the borrow checker after last use it could be a "reliable destructor". The problem is if you do it in a Future it cannot be guaranteed (this is a long and significnat problem that nobody was able to solve yet).

    You're absolutely right that closure ergonomics is not great but in many cases it can be solved with a ref wrapper that has Drop impl. (mutex guard etc)

    Martin Habovštiak at 2025-02-04 21:12:29

  152. @RishabhRD looks good to me, with my limited Rust knowledge. For ensuring that control returns to a (non-yield-once) coroutine, you would still have to use a proxy, but maybe you could embed resumption into the destructor of the proxy.

    Dave Abrahams at 2025-02-05 00:19:20

  153. One thing to keep in mind about the proxy, and I think this applies to @Kixunil's ref wrapper too: if you rely on drop() for safety, you are broken because std::mem::forget() has been deemed safe. If you dig into this discussion (which seems unrelated until you expand it) you can see some details. Of course if you only rely on it for correctness and not safety you can just explicitly forbid the use of forget on these things in documentation.

    Dave Abrahams at 2025-02-05 00:27:48

  154. Actually modify and read accessors were added to swift for efficiency reasons. Previously there were only get (return a value without mutating the receiver) and set (roughly taking a & in Rust terms, and mutating the receiver) accessors, which meant a read-modify-write would require an expensive copy.

    Dave Abrahams at 2025-02-05 00:32:39

  155. if you rely on drop() for safety, you are broken because std::mem::forget() has been deemed safe

    Right, if we could also forbid holding the yielded reference across await we could have the original scoped thread API, no need to create annoying and confusing scope closure. And perhaps if someone figures out how to make Future's drop reliable, holding it will be fine.

    Martin Habovštiak at 2025-02-05 06:53:35

  156. Now that the gen keyword has been successfully reserved as part of the recently-stabilized Rust 2024, what are the next steps here? I honestly don't think this tracking issue is doing us any good at this point; it exists to track RFC 2033, which exists to ask the question "do we even think we want coroutines in the language in any capacity whatsoever?", which no longer seems to be in question. Instead people seem to be treating this issue as though it is the tracking issue for coroutines in general, which it cannot be, because such an RFC does not exist, a fact which is muddied by the existence of this issue. To answer my own original question, it seems like the next step here is "write the RFC, at long last", and that this issue can be closed as successfully completed.

    bstrie at 2025-03-02 20:11:03

  157. Instead people seem to be treating this issue as though it is the tracking issue for coroutines in general, which it cannot be, because such an RFC does not exist

    Coroutines are far from the only feature with a tracking issue but no RFC yet. I don’t see the problem

    Jules Bertholet at 2025-03-02 20:46:39

  158. To answer my own original question, it seems like the next step here is "write the RFC, at long last", and that this issue can be closed as successfully completed.

    I guess it can stay open as well? Regardless, I'd like to put my hand up for contributing to such an RFC to drive this forward. Is there a working group? Should we form one?

    Thomas Eizinger at 2025-03-02 20:54:04

  159. The lang team is currently discussing the introduction of generators (https://github.com/rust-lang/rust/pull/137725), that is most likely a prerequisite to this.

    Jakub Beránek at 2025-03-02 21:17:41

  160. I think part of my frustration here is that this design space is so broad, and its implementation in Rust has so much history, and there's so much implicit context buried away who-knows-where, and so much of what we think we know about the design of coroutines from other languages may (or may not) be inapplicable to Rust due to its specific semantics and objectives, and RFC 2033 is so intentionally vague while also being overly specific to the needs of async/await while also being so ancient that we don't even know what the purpose of this tracking issue anymore, that it becomes effectively impossible for anyone to contribute.

    IMO, what we need is for someone to summarize the past eight years of experimentation and provide some informed conclusions as to what the feasible future directions of this feature will look like.

    Right now, the best summary that I have found is this obscure document from 2020, which nevertheless seems outdated based on what little insight I have into development since then: https://github.com/samsartor/lang-team/blob/master/src/design_notes/general_coroutines.md

    So I'd like to ask someone knowledgeable and authoritative to take that document (or whatever better resource may exist), update it for 2025, and publish it somewhere just so that we can have a foothold for understanding the next steps for this feature. Such a document could even become a pre-RFC if sufficiently confident.

    bstrie at 2025-03-03 20:41:05

  161. I have been programming in Rust for 5 years. I am making an experimental deterministic runtime with Rust. Coroutines/generators are instrumental to my engine. How would one get involved in the creation of the language or attend a design meeting?

    Best regards, Sol Midnight

    Sol Midnight at 2025-03-05 03:26:41

  162. There are multiple tracking issues of it so idk where it should appropriately be.

    The compiler not being able to infer type in this case is quite wild.

    #![feature(coroutines)]
    #![feature(coroutine_trait)]
    #![feature(stmt_expr_attributes)]
    
    use std::ops::Coroutine;
    
    fn main() {
        fn assert_coroutine(_: impl Coroutine<i32, Yield = ()>) {}
        assert_coroutine(#[coroutine] |x| {
            x.count_ones();
        });
    }
    
    error[E0282]: type annotations needed
      --> src/lib.rs:9:36
       |
    9  |     assert_coroutine(#[coroutine] |x| {
       |                                    ^
    10 |         x.count_ones();
       |         - type must be known at this point
       |
    help: consider giving this closure parameter an explicit type
       |
    9  |     assert_coroutine(#[coroutine] |x: /* Type */| {
       |                                     ++++++++++++
    
    For more information about this error, try `rustc --explain E0282`.
    error: could not compile `playground` (lib) due to 1 previous error
    

    Ayabin at 2025-04-15 06:53:19

  163. @discreaminant2809 - What do you think the type of x should be in that context?

    Eric Holk at 2025-04-15 20:42:06

  164. I have been programming in Rust for 5 years. I am making an experimental deterministic runtime with Rust. Coroutines/generators are instrumental to my engine. How would one get involved in the creation of the language or attend a design meeting?

    Best regards, Sol Midnight

    @solmidnight - I'd recommend reaching out in the t-lang Zulip channel. There are some other places to go from there, but that's a good starting point.

    Eric Holk at 2025-04-15 20:44:18

  165. @discreaminant2809 - What do you think the type of x should be in that context?

    The signature: impl Coroutine<i32, Yield = ()> x is at the resume argument position, which is i32 in this case (the resume argument type is the only generic so the one without = is it).

    Ayabin at 2025-04-15 21:11:13