Support move expressions in coroutine closures - #157738
Conversation
|
Some changes occurred to MIR optimizations cc @rust-lang/wg-mir-opt |
|
|
b119411 to
219db1c
Compare
This comment has been minimized.
This comment has been minimized.
219db1c to
0f62aac
Compare
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Hmm. Something seems off here. I think we need more tests around nested-move and async blocks, async closures, and gen blocks. For example I'd expect:
let c: Arc<String> = Default::default();
let future = async {
let f = async {
move(c.clone());
};
assert!( /* c ref count is 2 */ );
f.await;
assert!( /* c ref count is 1 */ );
drop(c);
};let c: Arc<String> = Default::default();
let future = async {
let f = async {
move(c.clone());
};
f.await;
drop(c);
};
println!("{c}"); // <-- ERROR: c is movedlet c: Arc<String> = Default::default();
let future = async {
let f = async {
move(move(c.clone()));
};
assert!( /* c ref count is 3 */ );
f.await;
assert!( /* c ref count is 2 */ );
drop(c);
};
println!("{c}"); // OK, prints ""
This comment has been minimized.
This comment has been minimized.
d7ddf08 to
cb24d7e
Compare
This comment has been minimized.
This comment has been minimized.
| @@ -0,0 +1,77 @@ | |||
| //@ edition: 2021 | |||
There was a problem hiding this comment.
✅ These match my expectations.
| }); | ||
| n | ||
| }; | ||
| assert_eq!(created.get(), 0); |
There was a problem hiding this comment.
❌ But this does not. I expected the move to execute when the closure was created. In other words, I expect these to be equivalent
let created = Cell::new(0);
let c = || {
let n = move({ created.set(created.get() + 1); created.get() });
n
};
assert_eq!(created.get(), 1);
assert_eq!(c(), 1);
assert_eq!(c(), 1);
assert_eq!(created.get(), 1);and
let created = Cell::new(0);
let c = async || {
let n = move({ created.set(created.get() + 1); created.get() });
n
};
assert_eq!(created.get(), 1);
assert_eq!(c().await, 1);
assert_eq!(c().await, 1);
assert_eq!(created.get(), 1);I think we are leaking the "desugaring" of async closures here somehow.
| yield n; | ||
| yield n + 1; | ||
| }); | ||
| assert_eq!(created.get(), 1); |
There was a problem hiding this comment.
✅ This matches my expectations
| PendingOnce::new().await; | ||
| yield Arc::strong_count(&value); | ||
| }); | ||
| assert_eq!(Arc::strong_count(&x), 2); |
| }); | ||
| yield ready_next(inner.as_mut()).unwrap(); | ||
| }); | ||
| assert_eq!(weak.strong_count(), 2); |
| }; | ||
| yield inner.next().unwrap(); | ||
| }; | ||
| assert_eq!(weak.strong_count(), 2); |
|
|
||
| fn main() { | ||
| let _ = || move(move(0)); | ||
| //~^ ERROR nested `move(expr)` requires another enclosing closure |
There was a problem hiding this comment.
✅ These errors match my expectations
| let value = move(z.clone()); | ||
| yield Arc::strong_count(&value); | ||
| }; | ||
| assert_eq!(Arc::strong_count(&z), 2); |
| f.await; | ||
| drop(c); | ||
| }; | ||
| println!("{c}"); //~ ERROR the type `Arc` does not implement `Copy` |
| let inner = outer(); | ||
| assert_eq!(Arc::strong_count(&v), 2); | ||
| assert_eq!(inner(), v.len()); | ||
| assert_eq!(Arc::strong_count(&v), 1); |
There was a problem hiding this comment.
✅ Interesting example
This comment has been minimized.
This comment has been minimized.
f217d5d to
bfbd93a
Compare
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
| assert_eq!(created.get(), 1); | ||
| assert_eq!(closure().next(), Some(1)); | ||
| assert_eq!(closure().next(), Some(1)); | ||
| assert_eq!(created.get(), 1); |
There was a problem hiding this comment.
✅ This matches expectations.
| let closure = iter! { || { | ||
| yield move(x.clone()); | ||
| }}; | ||
| assert_eq!(Arc::strong_count(&x), 2); |
There was a problem hiding this comment.
✅ This matches expectations.
| }}; | ||
| assert_eq!(Arc::strong_count(&x), 2); | ||
| let mut generator = closure(); | ||
| assert_eq!(Arc::strong_count(&x), 2); |
There was a problem hiding this comment.
✅ This matches expectations.
| assert_eq!(Arc::strong_count(&x), 2); | ||
| assert_eq!(generator.next(), None); | ||
| drop(yielded); | ||
| assert_eq!(Arc::strong_count(&x), 1); |
There was a problem hiding this comment.
❓ Huh, am I missing something here--- we didn't drop x nor did we drop the closure -- oh, perhaps it is a FnOnce?
| let moved = move(a.clone()); | ||
| yield (moved, b.len()); | ||
| }}; | ||
| assert_eq!(closure().next(), Some((String::from("a"), 3))); |
| assert_eq!(created.get(), 1); | ||
|
|
||
| let x = Arc::new(String::from("hello")); | ||
| assert_eq!(Arc::strong_count(&x), 1); |
There was a problem hiding this comment.
✅ This matches expectations.
| assert_eq!(Arc::strong_count(&x), 1); | ||
|
|
||
| let c = async || move(x.clone()); | ||
| assert_eq!(Arc::strong_count(&x), 2); |
There was a problem hiding this comment.
✅ This matches expectations.
| let c = async || move(x.clone()); | ||
| assert_eq!(Arc::strong_count(&x), 2); | ||
| let fut = c(); | ||
| assert_eq!(Arc::strong_count(&x), 2); |
There was a problem hiding this comment.
✅ This matches expectations.
| let fut = c(); | ||
| assert_eq!(Arc::strong_count(&x), 2); | ||
| drop(fut); | ||
| assert_eq!(Arc::strong_count(&x), 1); |
There was a problem hiding this comment.
✅ This matches expectations.
| let moved = move(a.clone()); | ||
| (moved, b.len()) | ||
| }; | ||
| assert_eq!(block_on(call_once(c)), (String::from("a"), 3)); |
There was a problem hiding this comment.
✅ This matches expectations.
|
@TaKO8Ki r=me after rebased |
bfbd93a to
2ed8d0d
Compare
|
This PR was rebased onto a different main commit. Here's a range-diff highlighting what actually changed. Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers. |
|
@bors r=nikomatsakis rollup=iffy |
…es, r=nikomatsakis Support move expressions in coroutine closures This adds `move(expr)` support for coroutine closures. - [x] Support for move expressions in coroutine closures - [x] Support for move expressions in async blocks RFC: rust-lang/rfcs#3968 Tracking issue: rust-lang#155050 Project goal: - rust-lang/goals#107 - https://rust-lang.github.io/rust-project-goals/2026/ergonomic-rc.html I used AI to write the tests and reviewed them myself. r? @nikomatsakis
…uwer Rollup of 9 pull requests Successful merges: - #157738 (Support move expressions in coroutine closures) - #160219 (Implement `Thread::os_id`) - #162449 (Prefer removing a redundant shared reference over reborrow) - #162494 (Ignore `self-in-const-generics` test for parallel frontend) - #162495 (Reserve items in `Extend` implementations) - #162238 (trait solver: Include implied outlives assumptions) - #162473 (Small `x perf` improvements) - #162489 (Clean up on upvar_tys) - #162500 (Move the `expect-item-after-attribute.rs` test to the correct directory)
Rollup merge of #157738 - TaKO8Ki:move-expr-coroutine-closures, r=nikomatsakis Support move expressions in coroutine closures This adds `move(expr)` support for coroutine closures. - [x] Support for move expressions in coroutine closures - [x] Support for move expressions in async blocks RFC: rust-lang/rfcs#3968 Tracking issue: #155050 Project goal: - rust-lang/goals#107 - https://rust-lang.github.io/rust-project-goals/2026/ergonomic-rc.html I used AI to write the tests and reviewed them myself. r? @nikomatsakis
View all comments
This adds
move(expr)support for coroutine closures.RFC: rust-lang/rfcs#3968
Tracking issue: #155050
Project goal:
I used AI to write the tests and reviewed them myself.
r? @nikomatsakis