For example, in the following snippet, Rc is held across yield point, thus it could be dropped on another thread, which breaks the !Send requirement on Rc. Also see "Tokio tutorial: Send bould".
// Current:
let iter = gen { // `-> impl Iterator + !Send`
let rc = Rc::new(...);
yield 12u32;
rc.do_something();
};
// Proposed:
let iter = gen { // `-> impl IntoIterator + Send`
let rc = Rc::new(...);
yield 12u32;
rc.do_something();
};
And I also learnt there has already been a trick to make the gen (or async) block Send via closure:
use std::rc::Rc;
fn is_send<T: Send>(_: T) {}
#[tokio::main]
async fn main() {
let x = async || {
let x = Rc::new(());
async {}.await;
drop(x); // Dropping Rc in another thread breaks `Rc: !Send`
};
is_send(x);
}
Aha. That's subtle! Thank you for the complete and convincing example!
Now I can understand the desired usage in yoshuawuyts's blogpost: it's fine for a gen block to be Send and moved to a thread. Rc is instantiated and used only on that thread. So it's fine to use Rc arcoss yield points.
let iter = gen { // `-> impl IntoIterator + Send`
let rc = Rc::new(...);
yield 12u32;
rc.do_something();
};
// ✅ Ok
thread::spawn(move || { // ← `iter` is moved
for num in iter { // ← `iter` is used
println("{num}");
}
}).unwrap();