This would probably be a good enough lint in practice. And in fact, IIRC futures are must_use
already, in which case this crate already has this lint without any need to annotate yield_
itself.
However, must_use
is imperfect here as it still allows wrong code like...
let f_a = co.yield_(a);
co.yield_(b).await;
f_a.await;
This crate's "yield" emulation is implemented by storing data in single-element shared storage and waiting for the generator's caller to fetch it. Instead, the above code will instantly overwrite "a" with "b" before waiting for the caller. Therefore, the sequence of generated outputs will be incorrect, missing "a".
What we really need for a 100% foolproof API here, is a way to guarantee that the future will be immediately awaited, which is stronger than what must_use
provides. Hence my macro proposal.
Also, this crate implements Generator::resume()
with a very simple future executor which is not capable of executing arbitrary futures, but only the ones produced by Co::yield_()
. Therefore, there is a temptation to also prevent the user from awaiting any future but those, which is something that they could reasonably attempt otherwise (maybe successfully maybe not, depending on the Future impl), by grepping for "invalid" use of .await
and reporting it as an error.
This cannot be done with declarative macros alone, but it could probably be done with a more sophisticated proc macro based design, which might also allow more radical syntax like this:
// NOTE: I did not actually try to write the macro, so I'm not sure if this works
#[generator(yield_ = i32)] // Adds a "co: Co<i32>" parameter, makes this an async fn
fn odd_numbers_less_than_ten() {
let mut n = 1;
while n < 10 {
yield_!(n); // Translates to co.yield_(n).await
n += 2;
}
/* arbitrary_other_future().await */ // #[generator] macro would reject this
}
This syntax would have the additional advantage of preventing the user from leaking the co
variable out of the generator, thus eliminating the memory safety hole of this crate's stack-pinned generators (if I understood the problem correctly) and making them safe to use.
...and of course, the macro above could support generator resume arguments if you want them:
// Yields the sum of all Some() resume args passed so far, terminates on None
#[generator(resume_args = Option<i32>, yield_ = i32)]
fn accumulator() {
let mut n = 0;
while let Some(resume_arg) = yield_!(n) {
n += resume_arg;
}
}
Note that @whatisaphone do not actually need to write the macro themselves, it can be implemented by a third party on top of the "genawaiter" crate (though resume arguments would probably work best with direct support from genawaiter). However, @whatisaphone may be interested in the possibility of providing a safe API to stack-pinned generators...