Skip to content

Support move expressions in coroutine closures - #157738

Merged
rust-bors[bot] merged 13 commits into
rust-lang:mainfrom
TaKO8Ki:move-expr-coroutine-closures
Sep 9, 2026
Merged

Support move expressions in coroutine closures#157738
rust-bors[bot] merged 13 commits into
rust-lang:mainfrom
TaKO8Ki:move-expr-coroutine-closures

Conversation

@TaKO8Ki

@TaKO8Ki TaKO8Ki commented Jun 11, 2026

Copy link
Copy Markdown
Member

View all comments

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:

I used AI to write the tests and reviewed them myself.

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

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.

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.

Comment thread tests/ui/move-expr/async-closures.rs Outdated
});
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.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I fixed this in d9885df.

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
@rust-bors

This comment has been minimized.

@TaKO8Ki
TaKO8Ki force-pushed the move-expr-coroutine-closures branch from f217d5d to bfbd93a Compare August 20, 2026 15:08
@rustbot

This comment has been minimized.

@TaKO8Ki
TaKO8Ki requested a review from nikomatsakis August 24, 2026 13:06
@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Aug 24, 2026
@rust-bors

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.

assert_eq!(created.get(), 1);
assert_eq!(closure().next(), Some(1));
assert_eq!(closure().next(), Some(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 expectations.

let closure = iter! { || {
yield move(x.clone());
}};
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.

✅ This matches expectations.

}};
assert_eq!(Arc::strong_count(&x), 2);
let mut generator = closure();
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.

✅ This matches expectations.

assert_eq!(Arc::strong_count(&x), 2);
assert_eq!(generator.next(), None);
drop(yielded);
assert_eq!(Arc::strong_count(&x), 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.

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

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 makes sense.

assert_eq!(created.get(), 1);

let x = Arc::new(String::from("hello"));
assert_eq!(Arc::strong_count(&x), 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 expectations.

assert_eq!(Arc::strong_count(&x), 1);

let c = async || move(x.clone());
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.

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

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 expectations.

let fut = c();
assert_eq!(Arc::strong_count(&x), 2);
drop(fut);
assert_eq!(Arc::strong_count(&x), 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 expectations.

let moved = move(a.clone());
(moved, b.len())
};
assert_eq!(block_on(call_once(c)), (String::from("a"), 3));

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 expectations.

@nikomatsakis

Copy link
Copy Markdown
Contributor

@TaKO8Ki r=me after rebased

@TaKO8Ki
TaKO8Ki force-pushed the move-expr-coroutine-closures branch from bfbd93a to 2ed8d0d Compare September 8, 2026 17:23
@rustbot

rustbot commented Sep 8, 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.

@TaKO8Ki TaKO8Ki added the llm-assisted An LLM-assisted PR as defined by the LLM policy. Requires ahead-of-time consent by assignee. label Sep 8, 2026
@TaKO8Ki

TaKO8Ki commented Sep 8, 2026

Copy link
Copy Markdown
Member Author

@bors r=nikomatsakis rollup=iffy

@rust-bors

rust-bors Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

📌 Commit 2ed8d0d has been approved by nikomatsakis

It is now in the queue for this repository.

@rust-bors rust-bors Bot added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Sep 8, 2026
JonathanBrouwer added a commit to JonathanBrouwer/rust that referenced this pull request Sep 8, 2026
…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
rust-bors Bot pushed a commit that referenced this pull request Sep 8, 2026
…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)
rust-bors Bot pushed a commit that referenced this pull request Sep 9, 2026
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
@rust-bors
rust-bors Bot merged commit cf24767 into rust-lang:main Sep 9, 2026
13 checks passed
@rustbot rustbot added this to the 1.100.0 milestone Sep 9, 2026
@TaKO8Ki
TaKO8Ki deleted the move-expr-coroutine-closures branch September 9, 2026 06:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

llm-assisted An LLM-assisted PR as defined by the LLM policy. Requires ahead-of-time consent by assignee. S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. 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