Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.
Sign upTracking issue for RFC 2033: Experimentally add coroutines to Rust #43122
Comments
aturon
added
B-RFC-approved
T-lang
labels
Jul 8, 2017
This comment has been minimized.
This comment has been minimized.
|
cc #43076, an initial implementation |
This comment has been minimized.
This comment has been minimized.
mitranim
commented
Jul 25, 2017
|
Copied from #43076: I'm using this branch for stream-heavy data processing. By streams I mean iterators with blocking FS calls. Because Python: def my_iter(iter):
for value in iter:
yield valueRust with generators:
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 |
Mark-Simulacrum
added
the
C-tracking-issue
label
Jul 27, 2017
jimmycuadra
referenced this issue
Aug 16, 2017
Open
Things blocking or slowing progress of Ruma #189
This comment has been minimized.
This comment has been minimized.
silene
commented
Aug 21, 2017
|
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 The object passed to 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 |
alexcrichton
added
the
A-generators
label
Aug 30, 2017
This comment has been minimized.
This comment has been minimized.
|
One subtle point that came up is how we handle the partially-empty boxes created inside of 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 |
da-x
referenced this issue
Oct 8, 2017
Closed
Borrow error when yielding with nested generators #45093
This comment has been minimized.
This comment has been minimized.
masonk
commented
Jan 7, 2018
•
|
I might be missing something in the RFC, but based on the definition of Here's an example of implementing the async/await pattern using coroutines in ES6. The generator yields Rust has a problem here because what's the type of TL;DR:
(Here are some more very interesting ideas for how to use two-way coroutines.) |
This comment has been minimized.
This comment has been minimized.
I don't know what you mean by "OIBIT". But at the yield point, you do not have a |
This comment has been minimized.
This comment has been minimized.
|
Looking at the API, it doesn't seem very ergonomic/idiomatic that you have to check if
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 |
This comment has been minimized.
This comment has been minimized.
jnferner
commented
Feb 28, 2018
•
|
Has there been any progress regarding the |
This comment has been minimized.
This comment has been minimized.
phaux
commented
Mar 2, 2018
https://internals.rust-lang.org/t/pre-rfc-generator-integration-with-for-loops/6625 |
This comment has been minimized.
This comment has been minimized.
Flupp
commented
Mar 28, 2018
|
I was looking at the current
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:
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 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. |
This comment has been minimized.
This comment has been minimized.
|
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 |
This comment has been minimized.
This comment has been minimized.
|
A |
This comment has been minimized.
This comment has been minimized.
|
Note that |
This was referenced Apr 3, 2018
This comment has been minimized.
This comment has been minimized.
Lisoph
commented
Apr 5, 2018
|
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");
}; |
This comment has been minimized.
This comment has been minimized.
|
I was hopeful that 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 (Note that 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 |
This comment has been minimized.
This comment has been minimized.
Lisoph
commented
Apr 5, 2018
|
Yeah, I was hoping as well impl Trait would do the trick, but couldn't get it to work either. Your I guess the only way is to introduce more syntax to annotate the types? |
This comment has been minimized.
This comment has been minimized.
|
Will the |
This comment has been minimized.
This comment has been minimized.
|
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):
The third point has actually happened already, but it doesn't help much since that required making |
This comment has been minimized.
This comment has been minimized.
|
I can confirm that the following definition of 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. |
This comment has been minimized.
This comment has been minimized.
|
Going to again point out what I mentioned earlier: rather than a single |
This comment has been minimized.
This comment has been minimized.
|
Why generator doesn't accept values on resume? |
This comment has been minimized.
This comment has been minimized.
|
@clarcharr you can implement such |
This comment has been minimized.
This comment has been minimized.
|
Any update on whether The strange behaviour mentiond in #43122 (comment) is still reproducible as well. |
This comment has been minimized.
This comment has been minimized.
|
@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. |
This comment has been minimized.
This comment has been minimized.
|
@clarcharr how do you write the pinned version in terms of a version of |
This comment has been minimized.
This comment has been minimized.
|
Oh, right. It's unfortunate we don't have a way to do a form of consume-and-drop with the |
Nemo157
referenced this issue
Nov 5, 2018
Merged
Use pinning for generators to make trait safe #55704
This comment has been minimized.
This comment has been minimized.
|
Why |
This comment has been minimized.
This comment has been minimized.
|
I'm fairly certain that |
This comment has been minimized.
This comment has been minimized.
|
@newpavlov note that only This also means you can have a fully safe generic I was recently wondering if adding an 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. |
This comment has been minimized.
This comment has been minimized.
|
@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 |
This comment has been minimized.
This comment has been minimized.
|
@Nemo157 |
This comment has been minimized.
This comment has been minimized.
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 |
This comment has been minimized.
This comment has been minimized.
|
You can distinguish between safe and unsafe generators, you will pass around by value "unsafe" generator, it will not implement |
This comment has been minimized.
This comment has been minimized.
|
With #55704 we can distinguish between potentially self-referential and guaranteed movable generators, they're named |
This comment has been minimized.
This comment has been minimized.
|
My point is that I am looking forward to using |
This comment has been minimized.
This comment has been minimized.
|
It's trivial to opt-out of pinning and use 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);
} |
This comment has been minimized.
This comment has been minimized.
|
So why use this approach instead of the |
This comment has been minimized.
This comment has been minimized.
|
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 For example, why is it that we can't just use |
This comment has been minimized.
This comment has been minimized.
|
If your generator is |
This comment has been minimized.
This comment has been minimized.
|
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. |
This comment has been minimized.
This comment has been minimized.
Why is that? I don't see why |
This comment has been minimized.
This comment has been minimized.
|
@newpavlov Just to make it clear: The reason for
If your struct does not need to pin anything, then a It is only when your struct needs to pin something that you need to deal with the complexity of In practice that means the only time you need to deal with But if you're creating standalone Generators then you don't need to deal with
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 And your 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 If instead we require The difference with your system and the With your system, you must manually create the But with |
bors
added a commit
that referenced
this issue
Jan 28, 2019
This comment has been minimized.
This comment has been minimized.
|
Has there been any discussions about avoiding more than one Another optimization question is about safe-to-move references: particularly, dereferenced |
This comment has been minimized.
This comment has been minimized.
|
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. |
aturon commentedJul 8, 2017
•
edited by alexcrichton
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