Skip to content

Conversation

@joshtriplett
Copy link
Member

@joshtriplett joshtriplett commented Aug 20, 2024

Provide a feature to simplify performing lightweight clones (such as of
Arc/Rc), particularly cloning them into closures or async blocks, while
still keeping such cloning visible and explicit.

A very common source of friction in asynchronous or multithreaded Rust
programming is having to clone various Arc<T> reference-counted objects into
an async block or task. This is particularly common when spawning a closure as
a thread, or spawning an async block as a task. Common patterns for doing so
include:

// Use new names throughout the block
let new_x = x.clone();
let new_y = y.clone();
spawn(async move {
    func1(new_x).await;
    func2(new_y).await;
});

// Introduce a scope to perform the clones in
{
    let x = x.clone();
    let y = y.clone();
    spawn(async move {
        func1(x).await;
        func2(y).await;
    });
}

// Introduce a scope to perform the clones in, inside the call
spawn({
    let x = x.clone();
    let y = y.clone();
    async move {
        func1(x).await;
        func2(y).await;
    }
});

All of these patterns introduce noise every time the program wants to spawn a
thread or task, or otherwise clone an object into a closure or async block.
Feedback on Rust regularly brings up this friction, seeking a simpler solution.

This RFC proposes solutions to minimize the syntactic weight of
lightweight-cloning objects, particularly cloning objects into a closure or
async block, while still keeping an indication of this operation.


This RFC is part of the "Ergonomic ref-counting" project goal, owned by
@jkelleyrtp. Thanks to @jkelleyrtp and @nikomatsakis for reviewing. Thanks to
@nikomatsakis for key insights in this RFC, including the idea to use use.

Rendered

@joshtriplett joshtriplett added T-lang Relevant to the language team, which will review and decide on the RFC. I-lang-nominated Indicates that an issue has been nominated for prioritizing at the next lang team meeting. labels Aug 20, 2024
@ghost
Copy link

ghost commented Aug 20, 2024

Personally, I don't feel that the non-closure/block use cases of this are really strong enough to warrant adding this, and the closure/block use case can be fixed with clone blocks.

The example

let obj: Arc<LargeComplexObject> = new_large_complex_object();
some_function(obj.use); // Pass a separate use of the object to `some_function`
obj.method(); // The object is still owned afterwards

could just be written as some_function(obj.clone()) with the only downsides being "that will still compile even if obj is expensive to clone" (Which is likely more easily solvable through a lint rather than a language feature), and not being able to remove redundant clones.

Which can presumably be solved either by making LLVM smarter about atomics for the specific case of Arc, or having an attribute on a clone impl that gives it the semantics use is being given here (Which would benefit all code that uses that type, not just new code that has been written to use .use)

The ergonomics of needing to clone in a block are annoying though, I agree, but that's a smaller feature by being able to do:

spawn(async clone {
    func1(x).await;
    func2(y).await;
});

and similarly for closures.

@joshtriplett
Copy link
Member Author

joshtriplett commented Aug 20, 2024

the closure/block use case can be fixed with clone blocks

The problem with a clone block/closure is that it would perform both cheap and expensive clones. A use block/closure will only perform cheap clones (e.g. Arc::clone), never expensive ones (e.g. Vec::clone).

Even without the .use syntax, async use blocks and use || closures provide motivation for this.

or having an attribute on a clone impl that gives it the semantics use is being given here (Which would benefit all code that uses that type, not just new code that has been written to use .use)

I think there's potential value there (and I've captured this in the RFC); for instance, we could omit a clone of a String if the original is statically known to be dead. I'd be concerned about changing existing semantics, though, particularly if we don't add a way for users to bypass that elision (which would add more complexity).

@Diggsey
Copy link
Contributor

Diggsey commented Aug 20, 2024

I'm afraid I'm pretty negative about this RFC.

Use trait

I don't like the Use concept as applied here: I don't think it makes sense to tie the concept of a "lightweight clone" to the syntax sugar for cloning values into a closure. Why can't I clone heavy-weight objects into a closure? It seems like an arbitrary distinction imposed by the compiler, when the compiler cannot possibly know what the performance requirements of my code are.

I could imagine there might be scenarios where knowing if a clone is light-weight is useful, but I don't think this is one of them.

.use keyword

I think the suffix .use form is unnecessary when you can already chain .clone(), and it's confusing for new users that .clone() requires brackets, whilst .use does not. Consistency is important. .use does not do anything that couldn't be done via a method, so it should be method - in general the least powerful language construct should be chose, in the same way that you wouldn't use a macro where a function would suffice.

However, x.use does not always invoke Clone::clone(x); in some cases the compiler can optimize away a use.

I don't like the "can" in this statement. Optimizations should fall into one of two camps:

  1. Automatic. These optimizations are based on the "as if" principle - ie. the program executes just as if the optimization was not applied, it just runs faster.
  2. Opt-in. This covers things like guaranteed tail calls, where the programmer says "I want this to be a tail-call" and the compiler returns an error if it can't do it.

Giving the compiler implementation a choice which has program-visible side-effects, and then specifying a complicated set of rules for when it should apply the optimization is just asking for trouble (eg. see C++'s automatic copy elision...) and I don't want to work in a language where different compilers might make the exact same code execute in significantly different ways.

use closure

If any object referenced within the closure or block does not implement Use (including generic types whose bounds do not require Use), the closure or block will attempt to borrow that object instead

I think this fallback is dangerous, as it means that implementing Use for existing types can have far-reaching implications for downstream code, making it a backwards compatibility hazard.

Motivation

Getting back to the original motivation: making reference counting more seamless, I think simply adding a syntax or standard library macro for cloning values into a closure or async block would go a long way to solving the issue... Potentially even all the way.

If a more significant change is needed, then I think this should be a type of variable binding (eg. let auto mut x = ...) where such variables are automatically cloned as necessary, but I hope such a significant change is not needed.

@joshtriplett
Copy link
Member Author

joshtriplett commented Aug 20, 2024

I've added a new paragraph in the "Rationale and alternatives" section explaining why async clone/clone || would not suffice:

Rather than specifically supporting lightweight clones, we could add a syntax
for closures and async blocks to perform any clones (e.g. async clone /
clone ||). This would additionally allow expensive clones (such as
String/Vec). However, we've had many requests to distinguish between
expensive and lightweight clones, as well as ecosystem conventions attempting
to make such distinctions (e.g. past guidance to write Arc::clone/Rc::clone
explicitly). Having a syntax that only permits lightweight clones would allow
users to confidently use that syntax without worrying about an unexpectedly
expensive operation. We can then provide ways to perform the expensive clones
explicitly, such as the use(x = x.clone()) syntax suggested in
[future possibilities][future-possibilities].

@joshtriplett
Copy link
Member Author

joshtriplett commented Aug 20, 2024

@Diggsey wrote:

I don't think it makes sense to tie the concept of a "lightweight clone" to the syntax sugar for cloning values into a closure. Why can't I clone heavy-weight objects into a closure?

You can; I'm not suggesting that we couldn't provide a syntax for that, too. However, people have asked for the ability to distinguish between expensive and lightweight clones. And a lightweight clone is less of a big deal, making it safer to have a lighter-weight syntax and let users mostly not worry about it. We could additionally provide syntax for performing expensive clones; I've mentioned one such syntax in the future work section, but we could consider others as well if that's a common use case.

I think the suffix .use form is unnecessary when you can already chain .clone()

That assumes that users want to call .clone(), rather than calling something that is always lightweight. If we separate out that consideration, then the question of whether this should be .use or a separate (new) trait method is covered in the alternatives section. I think it'd be more unusual to have the elision semantics and attach them to what otherwise looks like an ordinary trait method, but we could do that.

.use does not do anything that couldn't be done via a method, so it should be method

This is only true if we omitted the proposed elision behavior, or if we decide that it's acceptable for methods to have elision semantics attached to them. I agree that in either of those cases there's no particular reason to use a special syntax rather than a method.

I don't like the "can" in this statement. [...] Giving the compiler implementation a choice which has program-visible side-effects, and then specifying a complicated set of rules for when it should apply the optimization is just asking for trouble

This is a reasonable point. I personally don't think this would cause problems, but at a minimum I'll capture this in the alternatives section, and we could consider changing the elision behavior to make it required. The annoying thing about making it required is that we then have to implement it before shipping the feature and we can never make it better after shipping the feature. I don't think that's a good tradeoff.

Ultimately, though, I think the elisions aren't the most important part of this feature, and this feature is well worth shipping without the elisions, so if the elisions fail to reach consensus we can potentially ship the feature without the elisions. (Omitting the elisions entirely is already called out as an alternative.)

Getting back to the original motivation: making reference counting more seamless, I think simply adding a syntax or standard library macro for cloning values into a closure or async block would go a long way to solving the issue... Potentially even all the way.

See the previous points about people wanting to distinguish lightweight clones specifically. This is a load-bearing point: I can absolutely understand that if you disagree with the motivation of distinguishing lightweight clones, the remainder of the RFC then does not follow. The RFC is based on the premise that people do in fact want to distinguish lightweight clones specifically.

If a more significant change is needed, then I think this should be a type of variable binding (eg. let auto mut x = ...) where such variables are automatically cloned as necessary

I've added this as an alternative, but I don't think that would be nearly as usable.

@joshtriplett
Copy link
Member Author

joshtriplett commented Aug 20, 2024

@Diggsey wrote:

use closure

If any object referenced within the closure or block does not implement Use (including generic types whose bounds do not require Use), the closure or block will attempt to borrow that object instead

I think this fallback is dangerous, as it means that implementing Use for existing types can have far-reaching implications for downstream code, making it a backwards compatibility hazard.

While I don't think this is dangerous, I do think it's not the ideal solution, and I'd love to find a better way to specify this. The goal is to use the things that need to be used, and borrow the things for which a borrow suffices. For the moment, I've removed this fallback, and added an unresolved question.

@davidhewitt
Copy link
Contributor

Thank you for working on this RFC! PyO3 necessarily makes heavy use of Python reference counting so users working on Rust + Python projects may benefit significantly from making this more ergonomic. The possibility to elide operations where unnecessary is also very interesting; while it's a new idea to me, performance optimizations are always great!

I have some questions:

  • The name Use for the trait was quite surprising to me. Reading the general description of the trait and the comments in this thread, it seems like "lightweight cloning" or "shallow cloning" is generally the property we're aiming for. Why not call the trait LightweightClone or ShallowClone? (Maybe can note this in rejected alternatives?)

  • The RFC text doesn't make it clear to me why use & move on blocks / closures need to be mutually exclusive. In particular what if I want to use an Arc<T> and move a Vec<Arc<T>> at the same time; if I'm not allowed the move keyword then I guess I have to fall back to something like let arc2 = arc.use; and then moving both values? Seems like this is potentially confusing / adds complexity.

  • I would like to see further justification why the rejection of having Use provide automatic cloning for these types. I could only find one short justification in the text: "Rust has long attempted to keep user-provided code visible, such as by not providing copy constructors."

    • We already have user-provided code running in Deref operations for most (all?) of the types for which Use would be beneficial. Is it really so bad to make these types a bit more special, if it's extremely ergonomic and makes room for optimizations of eliding .clone() where the compiler can see it?
    • Further, I think Clone is already special: for types which implement Copy, a subtrait of Clone, we already ascribe special semantics. Why could we not just add ShallowClone as another subtrait of Clone which allows similar language semantics (but doesn't go as far as just a bit copy, which would be incorrect for these types)?

@kennytm
Copy link
Member

kennytm commented Aug 21, 2024

Since "Precise capturing" #3617 also abuses the use keyword this may be confusing to teach about the 3 or 4 unrelated usage of the keyword (use item; / impl Trait + use<'captured> / use || closure & async use { block } / rc.use).

@burdges
Copy link

burdges commented Aug 21, 2024

We should really not overload the usage of the keyword use so much, but ignoring the keyword..

Isn't it easier to understand if we've some macro for the multiple clones that run before the code that consumes them, but still inside some distinguished scope?

{
    same_clones!(x,y,z);
    spawn(async move { ... });
}

In this, the same_clones! macro expands to

let x = x.clone();
let y = y.clone();
let z = z.clone();

We use this multi-clones pattern outside async code too, so this non-async specific approach benefits everyone.

github-actions bot pushed a commit to rust-lang/rustc-dev-guide that referenced this pull request Apr 14, 2025
…tsakis

Ergonomic ref counting: optimize away clones when possible

This PR build on top of rust-lang/rust#134797. It optimizes codegen of ergonomic ref-counting when the type being `use`d is only known to be copy after monomorphization. We avoid codening a clone and generate bitwise copy instead.

RFC: rust-lang/rfcs#3680
Tracking issue: rust-lang/rust#132290
Project goal: rust-lang/rust-project-goals#107

r? `@nikomatsakis`

This PR could better sit on top of rust-lang/rust#131650 but as it did not land yet I've decided to just do minimal changes. It may be the case that doing what I'm doing regress the performance and we may need to go the full route of rust-lang/rust#131650.
cc `@saethlin` in this regard.
lnicola pushed a commit to lnicola/rust-analyzer that referenced this pull request Apr 28, 2025
…tsakis

Ergonomic ref counting: optimize away clones when possible

This PR build on top of rust-lang/rust#134797. It optimizes codegen of ergonomic ref-counting when the type being `use`d is only known to be copy after monomorphization. We avoid codening a clone and generate bitwise copy instead.

RFC: rust-lang/rfcs#3680
Tracking issue: rust-lang/rust#132290
Project goal: rust-lang/rust-project-goals#107

r? `@nikomatsakis`

This PR could better sit on top of rust-lang/rust#131650 but as it did not land yet I've decided to just do minimal changes. It may be the case that doing what I'm doing regress the performance and we may need to go the full route of rust-lang/rust#131650.
cc `@saethlin` in this regard.
@oli-obk oli-obk mentioned this pull request May 19, 2025
@Atry
Copy link

Atry commented May 20, 2025

I've added this as an alternative, but I don't think that would be nearly as usable.

I can't see why let auto is not usable. Could you elaborate?

@Atry
Copy link

Atry commented May 20, 2025

If a more significant change is needed, then I think this should be a type of variable binding (eg. let auto mut x = ...) where such variables are automatically cloned as necessary

I've added this as an alternative, but I don't think that would be nearly as usable.

I cannot find let auto in your "alternative" part of the RFC. Did I miss something?

@proski
Copy link

proski commented May 26, 2025

Very good proposal, thank you! The rationale is spot on.

I'd rather use more explicit names, even if they are longer. I imagine I would see Use (or Claim) and translate it to "fast clone" in my head. If so, the trait could be called FastClone to save me that translation. Good code is written with other developers in mind, so let's prioritize readability over saved keystrokes.

Likewise, the use keyword for closures could be automove, as that how I would read it anyway.

The .use "field" looks like weird magic, I'd rather use existing syntax, even if it's a macro, e.g. std::fastclone::clone!

I would not worry too much about compatibility with older editions and having to import some extra traits/macros.

I'm fine with typing more as long as I get an implementation where every field does what's expected of it (move, fast clone, copy) with or without prodding (but without extra variables and extra scopes, please) and doesn't do what's not expected (slow clone, unwanted move).

@ssokolow
Copy link

ssokolow commented Oct 14, 2025

While replying to someone on Lobste.rs, I realized part of why I think this is ill-fitted for Rust:

It's a recurring pattern in Rust that things like Drop and Deref can happen implicitly, but things like Borrow, Clone, and Try must happen explicitly, with the main exception I notice (auto-borrow of self in method calls) being very minor in effect.

Ideas like making automatic clone/refcount incrementing a property of a type rather than a property of the call site feel like the memory consumption analogue to silently acquiring locks as needed, despite the risk of deadlocks, or silently bubbling up Results instead using ?. (i.e. throw Exception semantics.)

On an abstract level, Rust seems pretty consistent about acquire-y operations being explicit but release-y operations being allowed to be implicit via Drop implementations, and incrementing a refcount is an acquire-y operation.

In fact, auto-borrow of self feels like it's part of some higher-level of abstract consistency, possibly relating to how Deref is abstractly like .unwrap() but infallible.

EDIT: Basically, as I see it, Rust is explicit in the acquisition, to encourage and aid you in being conservative in how much you acquire, when, and it's implicit and end-of-scope automatic in the release as a "Rust didn't originally have NLL" conservative choice for minimizing how long and how much you keep held. They're two sides of the same "hold as few resources as possible for as short a time as possible" coin and they harmonize well with how much can be checked through compile-time lifetime analysis.

...so, at worst, I wouldn't want to see "lightweight" clones made "simpler" at each call site than ? or & are. (i.e. something like reviving the @ sigil that was used in the pre-1.0 days for GCed pointers before Rc and Arc became standard library types, so refcounting increment would look like let a = @b.) ...though I still think that's too simple, given the role of the current design in making over-eager use of refcounting into a more visible code smell in the same way that dependence on a third-party crate does for classical inheritance.

(I'm pretty sure I've already stated my belief that Rc and Arc should be code smells... but that some problems just haven't had un-smelly solutions invented yet.)

EDIT: To be clear, I wrote this based on either me or someone else having already addressed the stance I take on why something like .use is the wrong syntax for more ergonomic explicitness, given the stated problem. I'm in favour of the idea to hang a "capture by cloning with explicit argument list" extension onto the closure syntax.)

@jcsoo
Copy link

jcsoo commented Oct 14, 2025

The idiom that we want to simplify is this:

let a = Thing::new();
{
   let _a = a.clone(); // or a.handle()
   call_with(move || _a);
}

The previously described .use has downsides that others have mentioned above.

The form move use || a has the issue that the use may be far away from the variable, and the cloning is implicit.

The form move use(a) || a is closer to being explicit but has the distance issue and requires listing each cloned variable in two places.

It seems to me that this could be solved by a new prefix operator ^. One could rewrite the first example using the operator like this:

let a = Thing::new();
call_with(move || ^a);

This operator could be read as "clone from outer context" (or "lightweight clone from outer context" if we want to restrict its usage to those types) and would only be allowed in function-defining contexts such as closures and async blocks.

This seems to be best of both worlds - an explicit operator that is easy to type and visually appears right next to the variable name.

Its advantages are even clearer when you have a mix of variables that you want to clone and not clone:

let a = LightThing::new();
let b = HeavyThing::new();
let c = LightThing::new();

call_with(move || (^a, b, ^c));

is rewritten into

let a = LightThing::new();
let b = HeavyThing::new();
let c = LightThing::new();

{
   let _a = a.clone();
   let _c = c.clone();
   call_with(move || (_a, b, _c));
}

It also could support cloning multiple times in a block:

let a = Thing::new();

call_with(move || (^a, ^a));

would be rewritten as

let a = Thing::new();

{
   let _a_1 = a.clone();
   let _a_2 = a.clone();
   call_with(move || (_a_1, a_2));
}

@dhardy
Copy link
Contributor

dhardy commented Oct 14, 2025

The form move use(a) || a is closer to being explicit but has the distance issue and requires listing each cloned variable in two places.

This is still a big improvement over the status quo: much less verbose than let a = a.clone();, does not require a new scope (or new var name), and easily inlineable.

Good enough IMO, though clone(a) || a would be better if only it were a keyword.

@boxedair
Copy link

boxedair commented Oct 15, 2025

Closure captures also allows you to introduce some kind of sigil syntax sugar through macros to annotate which bindings are "automatically captured". I imagine something like this could be useful for dioxus like code:

#[component]
fn Compoonent(state: Arc<State>) {
    let x = || ^state.things();
}

// The macro can do simple syntax transformation into:

#[component]
fn Compoonent(state: Arc<State>) {
    let x = use(state) || state.things();
}

@ssokolow
Copy link

My issue with the proposed ^ syntax is that using move captures as a way to send things to a new thread already feels too implicit and ^ has its own "distance issue" relative to that.

@alextechcc
Copy link

alextechcc commented Oct 31, 2025

Any time I see this pop up I keep thinking that for the years I used C++ I never had this problem, I just used the explicit capture syntax. My vote is for copying that success, something like:

clone (a) || {
    // a is cloned
}
clone, move (a, b) || {
    // a and b are moved, rest are cloned
}

This matches Rust Analyzer's closure capture hints, and is very similar to the reclone crate , almost identical to the closure crate. It also fits right beside the existing move syntax at the start of the lambda. Fundamentally - putting all the stuff that a lambda captures all in one place feels much more easy to read and explicit than scanning through the function body for any code that calls .use (what happens if you call .use in one place and not in another place?), or reading the docs for every single type listed to see if it implements Use. I use those crates - the ergonomics are nice - it's explicit, I get to choose at the call site if I consider a type "cheap", the compiler tip for a moved-value error is obvious. I don't need more.

@kennytm
Copy link
Member

kennytm commented Nov 1, 2025

As stated before the clone(a) || { } syntax won't work unless clone is reserved as a keyword. The current strawperson proposed syntax is move(a.clone()) || { }.

@matthieu-m
Copy link

I think one alternative which hasn't been explored is tail position capture information.

As a parallel, when writing a function, one writes:

fn a_function<A, B, C, D>(an_argument: A, another_argument: B, yet_another_argument: C) -> Vec<D>
where
    A: SomeTrait,
    B: AnotherTrait,
    C: YetAnotherTrait;

The main advantage of doing so is that the function signature (arguments & result type) are obvious at a glance, without the fluff getting in the way.

The same consideration applies to capture information.

When considering a slightly more realistic example -- scaled up with more likely identifier lengths, number of captured identifiers, and number of arguments -- the upfront syntax makes it harder to see where the signature really begins:

move(a_capture.clone(), b_capture.clone(), c_capture.clone(), d_capture.clone()) |an_argument, another_argument, yet_another_argument| -> AResultType {
    ...
}

Formatting may help, though it looks a bit awkward to me:

move(a_capture.clone(), b_capture.clone(), c_capture.clone(), d_capture.clone())
|an_argument, another_argument, yet_another_argument| -> AResultType {
    ...
}

A tail position syntax, however, puts the signature upfront, regardless of formatting:

|an_argument, another_argument, yet_another_argument| -> AResultType
with
     clone(a_capture, b_capture, c_capture, d_capture),
{
    ...
}
|an_argument, another_argument, yet_another_argument| -> AResultType with clone(a_capture, b_capture, c_capture, d_capture),
{
    ...
}

It's less clear what parsing ambiguities do arise from this choice, though. It's possible that a contextual with keyword could help here, though that may require an edition.

@iago-lito
Copy link

iago-lito commented Nov 3, 2025

The current strawperson proposed syntax is move(a.clone()) || { }.

Since this reads as "move the clone", is there any chance to have it directly inline, as in:

// Instead of:
let c = move(long_name.clone()) |arg| long_name.method(arg);
// Something like:
let c = |arg| long_name.clone().move.method(arg);

?

@teohhanhui
Copy link

@iago-lito Then you'd have to scan the closure body to see what's being cloned, which makes it hard to follow / breaks the explicit flow.

@iago-lito
Copy link

iago-lito commented Nov 3, 2025

Agreed. The ergonomic gain only makes sense in small closures like this one. I guess the alternative we could document to avoid having to repeat long_names is something along:

let c = move(l = long_name.clone()) |arg| l.method(arg);

@theypsilon
Copy link

One step further in ergonomics beyond explicit capture clauses would be allowing the explicit activation of behavioral traits, which would result in different move semantics.

For example, given a Handle trait that marks light cloning and has a single activation method that returns the clone:

let a: Handle
let b: Handle
let closure = use<Handle> || {
    // a and b are clones here, because we are explicitly calling the activation of the Handle trait.
}

Would be equivalent to the explicitly doing this:

let a: Handle
let b: Handle
let closure = move(a = a.clone(), b= b.clone()) || {
    // a and b are clones here
}

The first example could also be explicitly restricted with use<Handle>(a) || {...} // a cloned but not b.

The main advantage, other than being still quite explicit, is that you could add different capture semantics by just introducing more traits. But that could also be understood as a disadvantage, since this complicates the understanding of Traits a bit more.

use<Handle> also has some value outside of a capture clause. You might want to do a.use<Handle>() (or directly calling the activator method of Handle) instead of calling a.clone(), because you want to ensure that nobody changes the type of a to something with an expensive clone (which shouldn't implement the Handle trait).

@ssokolow
Copy link

ssokolow commented Nov 7, 2025

Seeing https://smallcultfollowing.com/babysteps/blog/2025/11/05/bikeshedding-handle/ made me realize another reason I'm opposed to this:

Testing something like this in Rust itself, rather than in a third-party crate, feels like an abuse of power... as if boats had skipped making fehler a crate where the ecosystem could properly express that it's not a good fit, and gone straight to getting it in Rust.

Why does this get to be fast-tracked into the 2025H2 goals where it can potentially become an "Oh, sorry. Too late to get rid of it. Crates are depending on it now." when things like fehler and delegate, which I see as equally ill-fitted to Rust's design philosophy, have to demonstrate their appropriateness as third-party crates first?

(Especially when the perception that "things in the standard library are more trustworthy because supply chain attacks" will skew people's willingness to start depending on it compared to a proper trial run as a third-party crate.)

EDIT: I wouldn't mind as much if this were something that could be forbidden on a codebase-by-codebase basis using a Clippy lint. It'd be a drift toward "C++ is many little dialects and a maintainable codebase must use policy and tooling to keep contributors within one", but that's still tolerable at this stage. ...but the effects of "making costs less explicit" in a transitive dependency propagate throughout the codebase and show up as a flamegraph or heap trace that has no obvious places to fix.

@Abdillah
Copy link

Abdillah commented Nov 9, 2025

The proposed syntax of .use doesn't make sense for new user (and me) but the part in Future Possibilities resonates (the use (move y) || {} part).

So, I agreed to folks that proposed something like this:

let closure = use (move a, b = b.clone(), c_copy = c.clone()) || {}

But, remember that this reduces the ergonomic of the case where user just want to move their variables. How about upgrades the current move syntax.

// Strict mode
let closure = move (a, b = b.clone(), c_copy = c.clone(), d_typed: Box<_> = Box::new(d)) || {}

// Backward compatible loose mode
let closure = move || {}

Since it is always a move operation whether you want to pass the ownership or its (mut) reference / clone / boxed / ptr. We move things from parent boundary to closure.


Formatting, in my opinion, the current move syntax is already awkward. By estimating rustfmt current behavior, it would be something like this (I prefer the verbatim move can be clumped on the first line, resulting in their one-line grouping, but rustfmt may differs):

fn main() {
    let closure = move (
        a, a1, a2, a3,
        b = b.clone(), 
        c_copy = c.clone(), 
        d_typed: Box<_> = Box::new(d)
    ) |arg_a,
       arg_b,
       long_arg_c,
       some_typed_d: Option<u32>,
       arg_e,
       arg_f,
       arg_long_long_g| {
        println!("{arg_a} {arg_b} {long_arg_c}");
    };
}

@Nadrieril
Copy link
Member

@ssokolow I think you may be misunderstanding what is happening regarding this feature. I do think there may be an experimental nightly feature being developped in the compiler for this, but that's a normal thing we do to get a feel for features. We have processes for that: the experiment stays nightly-only with a "this feature is incomplete, be warned" warning to discourage people from depending on it.

It does sometimes happen that a feature is useful and complete enough that people start depending on it, but we're far from that with this one. And we have in the past ripped out such experiments from the compiler if they're not getting approved, that's the whole point of nightly features.

The reason this isn't a third-party crate is that we're talking about a new language feature. This can't just be implemented with a macro, or indeed we would have started with that. If you're worried about the Handle trait specifically, know that this isn't even on nighly yet; the blog post you link to is only a proposal that Niko made as he explores the design space.

Why does this get to be fast-tracked into the 2025H2 goals

Because the lang team felt that this particular issue was important to solve, based on what they've seen of what users stumble on. Note that the project goal isn't about this particular RFC, it's about finding some solution to the problem of ergonomic Arc etc. I wouldn't even say that this RFC is the leading proposal right now.

Happy to continue this discussion on Zulip if you see something I don't.

@ssokolow
Copy link

ssokolow commented Nov 9, 2025

Note that the project goal isn't about this particular RFC, it's about finding some solution to the problem of ergonomic Arc etc. I wouldn't even say that this RFC is the leading proposal right now.

OK, I admit I lost perspective on that part. Once I've decided whether I'm up to creating a Zulip account, you may see me there.

@egasimus
Copy link

move() || {} reads a bit too much like a Boolean OR between a function call and a block, IMO. Could a let..in syntax be worth considering?

let closure = a.clone(), b.clone() in move || {
    a.something();
    b.something();
};
let a.clone(), b.clone() in closure = move || {
    a.something();
    b.something();
};

@kennytm
Copy link
Member

kennytm commented Nov 10, 2025

I wouldn't even say that this RFC is the leading proposal right now.

😕 ok that makes me confused, if we are back to the drawing board why is this RFC still open

@Nadrieril
Copy link
Member

if we are back to the drawing board why is this RFC still open

Closing this would mean a decision that this really isn't the right solution; I didn't want to give the impression that there's any consensus like that. I mostly read blogs and issues and Zulip chats sometimes and it looked to me like the "explicit captures" idea was gaining traction is all. I don't feel like this one has been discarded at all.

@Abdillah
Copy link

There actually some blog posts about this by @nikomatsakis:

It contains what I've proposed above for syntax.

I wouldn't even say that this RFC is the leading proposal right now.

Is there other RFC related to this?

@Nadrieril
Copy link
Member

Is there other RFC related to this?

Not that I'm aware of no, the proposals I've seen are mostly Niko's posts :)

@iago-lito
Copy link

The current strawperson proposed syntax is move(a.clone()) || { }.

Since this reads as "move the clone", is there any chance to have it directly inline, as in:

// Instead of:
let c = move(long_name.clone()) |arg| long_name.method(arg);
// Something like:
let c = |arg| long_name.clone().move.method(arg);

?

There is discussion on some kind of related idea on zulip though, using .super instead of .move IIUC.

@kennytm
Copy link
Member

kennytm commented Nov 14, 2025

There is discussion on some kind of related idea on zulip though, using .super instead of .move IIUC.

The super expression means evaluating the expression in the "super" context, so the following two are equivalent:

let g = move || f(e().super);
<=>
let g = {
    let _temp = e();
    move || f(_temp);
};

This is basically the abuse of super let I mentioned previously.

Now IMO postfix .super should not be compared to .await because you aren't supposed to a local variable inside the super expression (the borrow checker will catch this, but still):

let g = move |x| x.clone().super; // ???

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

I-lang-radar Items that are on lang's radar and will need eventual work or consideration. T-lang Relevant to the language team, which will review and decide on the RFC.

Projects

None yet

Development

Successfully merging this pull request may close these issues.