Skip to content

Support move expressions in coroutine closures - #157738

Open
TaKO8Ki wants to merge 10 commits into
rust-lang:mainfrom
TaKO8Ki:move-expr-coroutine-closures
Open

Support move expressions in coroutine closures#157738
TaKO8Ki wants to merge 10 commits into
rust-lang:mainfrom
TaKO8Ki:move-expr-coroutine-closures

Conversation

@TaKO8Ki

@TaKO8Ki TaKO8Ki commented Jun 11, 2026

Copy link
Copy Markdown
Member

This adds move(expr) support for coroutine closures.

  • Support for move expressions in coroutine closures
  • Support for move expressions in async blocks

RFC: rust-lang/rfcs#3968
Tracking issue: #155050
Project goal:

r? @nikomatsakis

@rustbot

rustbot commented Jun 11, 2026

Copy link
Copy Markdown
Collaborator

Some changes occurred to MIR optimizations

cc @rust-lang/wg-mir-opt

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Jun 11, 2026
@rustbot

rustbot commented Jun 11, 2026

Copy link
Copy Markdown
Collaborator

nikomatsakis is currently at their maximum review capacity.
They may take a while to respond.

@TaKO8Ki
TaKO8Ki force-pushed the move-expr-coroutine-closures branch from b119411 to 219db1c Compare June 22, 2026 17:35
@rustbot

This comment has been minimized.

@TaKO8Ki
TaKO8Ki force-pushed the move-expr-coroutine-closures branch from 219db1c to 0f62aac Compare July 16, 2026 11:38
@rustbot

This comment has been minimized.

@nikomatsakis nikomatsakis left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 moved
let 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 ""

View changes since this review

Comment thread tests/ui/move-expr/async-blocks.rs Outdated
@rust-bors

This comment has been minimized.

@TaKO8Ki
TaKO8Ki force-pushed the move-expr-coroutine-closures branch from d7ddf08 to cb24d7e Compare August 12, 2026 10:28
@rustbot

rustbot commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

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.

@nikomatsakis nikomatsakis left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The behavior around async closures still seems off to me

View changes since this review

@@ -0,0 +1,77 @@
//@ edition: 2021

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ These match my expectations.

});
n
};
assert_eq!(created.get(), 0);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❌ 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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ This matches my expectations

PendingOnce::new().await;
yield Arc::strong_count(&value);
});
assert_eq!(Arc::strong_count(&x), 2);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ As does this

});
yield ready_next(inner.as_mut()).unwrap();
});
assert_eq!(weak.strong_count(), 2);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ As does this

};
yield inner.next().unwrap();
};
assert_eq!(weak.strong_count(), 2);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ As does this


fn main() {
let _ = || move(move(0));
//~^ ERROR nested `move(expr)` requires another enclosing closure

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ These errors match my expectations

let value = move(z.clone());
yield Arc::strong_count(&value);
};
assert_eq!(Arc::strong_count(&z), 2);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ As does this

f.await;
drop(c);
};
println!("{c}"); //~ ERROR the type `Arc` does not implement `Copy`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ As does this

let inner = outer();
assert_eq!(Arc::strong_count(&v), 2);
assert_eq!(inner(), v.len());
assert_eq!(Arc::strong_count(&v), 1);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Interesting example

@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Aug 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants