Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clone into closures #2407

Open
jonhoo opened this issue Apr 19, 2018 · 52 comments
Open

Clone into closures #2407

jonhoo opened this issue Apr 19, 2018 · 52 comments
Labels
T-lang Relevant to the language team, which will review and decide on the RFC.

Comments

@jonhoo
Copy link
Contributor

jonhoo commented Apr 19, 2018

When working with move closures, and especially when spawning new threads, I often find myself having to clone a bunch of state (usually Arcs, but not always). This is either because I want that state to be shared between many instances of a closure or with the closure and the code below the closure (e.g., Arc/Rc), or for mundane reasons like I need to have a String in each closure, and since thread::spawn requires 'static, I need to clone. In futures-based code, this happens a lot with Rc<Self> where I need to carry self along into futures chained with and_then. Usually, this results in code like this:

for _ in 0..nthreads {
    let foo = Arc::clone(&foo);
    let bar = Arc::clone(&bar);
    let baz = Arc::clone(&baz);
    thread::spawn(move || {

This appears a lot in the wild. Some very quick sampling yields: [1], [2], [3], [4], [5], [6], [7], [8].

Even if you're just spawning a single thread, you often need to use weirdly tweaked variable names. For example, this code:

let foo = Arc::new(...);
thread::spawn(move || {
    foo.baz();
});
foo.baz();

must be written as

let foo = Arc::new(...);
let foo_clone = foo.clone();
thread::spawn(move || {
    foo_clone.baz();
});
foo.baz();

or if you want to retain the foo name (which you often do), you need to introduce an extra artificial scope:

let foo = Arc::new(...);
{
    let foo = foo.clone();
    thread::spawn(move || {
        foo.baz();
    });
}
foo.baz();

I don't have a concrete proposal for how this would be fixed. It'd be really neat to have something like clone ||, but that's probably overly simplistic as often you want to move some variables and clone others. Some better ergonomics for this would be great!

@oli-obk
Copy link
Contributor

oli-obk commented Apr 20, 2018

A c++-lambda-ish macro could do all that boilerplate for us.

@mcarton
Copy link
Member

mcarton commented Apr 20, 2018

Lambda captures are one of the rare points where I think C++ did it better than Rust: they are explicit and you can express how to capture on a per-capture basis (in Rust that would be by reference, by Clone, by move, by some arbitrary expression (C++ allows [x=f(y)])).

@rpjohnst
Copy link

I like the idea of clone ||, I run into this quite a bit.

For reference, C++-style explicit lambda captures were considered pre-1.0. IIRC the reason for what we have now was that it requires fewer language features- you can get the same effects via the combination of a) default capture-by-reference and b) move closures that can capture temporary bindings.

In the limit it's not even very different syntactically:

[x = abc, y = def, &z]() { ... x ... y ... z }
{ let x = abc; let y = def; let z = &z; move || { ... x ... y ... z } }

I think what tips the balance is that C++'s equivalent of .clone() is the default there, so when dealing with reference counting Rust has quite a bit more noise. So in the interest of keeping clone explicit (doing away with it is a question for another thread, I think), clone || is certainly a nice way to do things.

The hard part is that move || has a natural opt-out- you selectively capture-by-move &Ts. That still works for clone, but I'm not sure if there's a great way to opt-out "half way" and selectively move Ts.

@shepmaster
Copy link
Member

macro could do all that boilerplate

I can't find the one I'm thinking about, but I know that macros exist for this. A quick search found:

Although one of those looks real old.

@RalfJung
Copy link
Member

RalfJung commented Apr 21, 2018

Scala has a proposal something similar to C++'s explicit capturing annotations in the form of spores. I think the corresponding Rust syntax would look a lot like what @rpjohnst suggested above

use std::sync::Arc;
use std::sync::Mutex;
use std::thread;

fn main() {
    let foo = Arc::new(Mutex::new(0));
    thread::spawn({ let foo = foo.clone(); move || {
        let _x = foo.lock();
    }});
    let _y = foo.lock();
}

@jonhoo
Copy link
Contributor Author

jonhoo commented Apr 21, 2018

While that syntax seems fine if you only need to manipulate a few variables, it gets quite hairy in situations like this. That's why I proposed copy || so that you could change the default behavior to clone. I think it's quite common to either copy most things or move most things into a closure (especially in the context of thread::spawn or making futures).

@Centril Centril added the T-lang Relevant to the language team, which will review and decide on the RFC. label Apr 22, 2018
@SoniEx2
Copy link

SoniEx2 commented Apr 28, 2018

What about a clone_all macro?

Hygiene stops at passed-in identifiers. A macro can take that identifier and create a let binding with it.

You can have let $name = $name.clone().

Maybe this macro should be built-in: https://play.rust-lang.org/?gist=f3e8daccce06320e831aeb29dc26ea5c&version=stable&mode=debug

@SoniEx2
Copy link

SoniEx2 commented Apr 28, 2018

So this:

        let args = Arc::clone(args);
        let quiet_matched = quiet_matched.clone();
        let paths_searched = paths_searched.clone();
        let match_line_count = match_line_count.clone();
        let paths_matched = paths_matched.clone();
        let bufwtr = Arc::clone(&bufwtr);
        let mut buf = bufwtr.buffer();
        let mut worker = args.worker();

Becomes:

        let args = Arc::clone(args);
        clone_all!(quiet_matched, paths_searched, match_line_count, paths_matched);
        let bufwtr = Arc::clone(&bufwtr);
        let mut buf = bufwtr.buffer();
        let mut worker = args.worker();

It should also be possible to modify the macro to take optional mut before $i:ident, so you can do this:

clone_all!(a, mut b, c);

@SoniEx2
Copy link

SoniEx2 commented Apr 28, 2018

Aaand I just make a crate for it. because y'know, I just had to. https://crates.io/crates/clone_all

@burdges
Copy link

burdges commented Apr 29, 2018

There are many scenarios where you should use either Cow::from(..)/Cow::Borrowed(..) or else do let foo = Arc/Rc::new(foo) followed by .clone(), instead of .clone() itself.

In any case, you could address the syntax directly without special casing clone with capture expressions that evaluate just outside the closure being created, so

foo(|..| { ... capture { Cow::from(bar) } ... })

becomes

{  let capture0 = Cow::from(bar);  foo(|..| { ... capture0 ... })  }

We avoid noise around the |..| too, while supporting more scenarios ala Cow.

Ideally, you could do this via a capture! proc macro crate, rather extending rust itself, which avoids any debate about "complexity budget". I've never looked into proc macros that access the AST outside their call site, but if that works then that's clearly the way to go.

@Diggsey
Copy link
Contributor

Diggsey commented Apr 29, 2018

There's this enclose macro that's used in a load of places including stdweb:

macro_rules! enclose {
    ( ($( $x:ident ),*) $y:expr ) => {
        {
            $(let $x = $x.clone();)*
            $y
        }
    };
}

Example use:
https://github.com/rust-webplatform/rust-todomvc/blob/master/src/main.rs#L142

@lukaspiatkowski
Copy link

lukaspiatkowski commented Jul 10, 2018

@SoniEx2 would you consider adding this to your crate? https://gist.github.com/rust-play/1cc71ed137157bb8115a9f063aabbea3

It allows using clone_all! on self.attribute:

struct A {
    x: String,
    y: String,
    z: String,
}
impl A {
    fn foo(&self) {
        clone_all!(self.x, self.y, self.z);
        (move || {
            println!("{} {} {}", x, y, z);
        })();
    }
}

@SoniEx2
Copy link

SoniEx2 commented Jul 10, 2018

@lukaspiatkowski I encourage ppl to fork the things I've made. The whole point of free software is the ability to fork things, so please fork my things! (I'm not sure why ppl don't fork things...)

@lukaspiatkowski
Copy link

lukaspiatkowski commented Jul 10, 2018

@SoniEx2 Sure, I never got the hang of it, every time I try to contribute to open source libraries the authors are not very helpful, so its hard to start doing so. I guess it comes with practice.

I have send a pull request: https://bitbucket.org/SoniEx2/clone_all/pull-requests/1

@cauebs
Copy link

cauebs commented Aug 16, 2018

This is a bit out of this issue's scope, but related: what if I want to selectively move variables into the closure? One example in which this came up today:

let v = vec!["a", "b", "c"];

rayon::scope(|s| {
    for i in 0..9 { // `i` does not live long enough
        s.spawn(|_| {
            println!("{}", v[i % 3])
        });
    } // borrowed value only lives until here
}); // borrowed value needs to live until here

println!("{:?}", v);

playground link

I really don't want to move v, nor do I want to clone it. But I have to move i.
However, today I can only either move everything or capture everything by reference.

This is a very simple example, but as shown in some comments above, it's not unusual to come across situations in which the workarounds are quite verbose. There's a clear need (in my view) of more granularity.

I don't have any good suggestions at the moment, but something analogous to C++'s capture list would be handy. This macro (linked above as well) is one way to handle this, but I really think there should be a solution built into the language.

@RalfJung
Copy link
Member

@cauebs move is strictly more general than not moving. To capture by reference, do something like

{ let v = &v;
  move |...| {
  /* use v, it will be a reference */
  }
}

This is how it looks in your example. It costs two extra lines, which is not great but also not horrible.

@shepmaster
Copy link
Member

shepmaster commented Aug 16, 2018

Or nikomatsakis' suggested form of the same, which places the borrow in the closest possible location, right when providing the closure:

rayon::scope(|s| {
    for i in 0..10 {
        s.spawn({
            let v = &v;
            move |_| println!("{}", v[i % 3])
        });
    }
});

This may be what @RalfJung meant, but the borrow can occur later than shown so I was a bit confused. Normally I'd have expected to see their code as:

rayon::scope(|s| {
    let v = &v;
    for i in 0..10 {
        s.spawn(move |_| println!("{}", v[i % 3]));
    }
});

@cauebs
Copy link

cauebs commented Aug 16, 2018

@RalfJung As I said, it was a simple example. But imagine there were dozens of variables that I wanted to be captured by reference, and only a single one that I wanted to be moved. That one is the exception, and that should be expressable in the code. I shouldn't have to change the default (moving everything) and treat all the others as exceptions.

I come from Python and have, with time, learned that if something is simple to explain (and reasonably common), it should be just as simple to encode. Otherwise, the language might be lacking some expressiveness.

@Ixrec
Copy link
Contributor

Ixrec commented Aug 16, 2018

@cauebs AFAIK you can also do let v = *v; or something similar above the closure to express "capture v by move", just as let v = &v; expresses "capture v by ref".

The idea of capture lists has been raised many times, but I've never seen a concrete syntax proposal that's significantly more concise than adding a few let statements before the closure without becoming cryptic. I don't think any language besides C++ has ever needed capture lists, and I believe C++ only needs them because it would be wildly unsafe to try and elide them in that language.

@SimonSapin
Copy link
Contributor

I think let v = *v; would do something different (e.g. dereference a String to str), but let v = {v}; (or just {v} if there’s only one use site) might be a way to force a move: https://bluss.github.io/rust/fun/2015/10/11/stuff-the-identity-function-does/

gakonst added a commit to interledger/interledger-rs that referenced this issue Jun 23, 2019
@phhusson
Copy link

I think @Diggsey enclose! is a very neat solution to this, would it make sense to have an RFC specifically to have this in stdlib?

@AberrantWolf
Copy link

AberrantWolf commented May 2, 2020

I independently ran into this myself. Is there a reason we wouldn't want to add a bit of extra syntax specifically for cloning bindings at the point of closure? The suggestion I thought up was a clone keyword, like the move keyword.

let clonable = ...;
// compiler assumes _.clone() for all bindings used within the closure
let c = clone || { clonable.do_something(); };
clonable.do_something_else();

Also, I considered you might want to mix move and clone:

// compiler clones only the specified binding and moves the rest
let c = move + clone clonable || {clonable.do_something;};

or possibly even cloning multiples:

// compiler clones the specified bindings, and moves the rest
let c = move + clone{clonable1, clonable2} || {...etc...};

The macro solution is interesting, but it feels to me more like a workaround to something that should be in the language.

@Pauan
Copy link

Pauan commented May 2, 2020

@AberrantWolf Using macros isn't considered a workaround, it's considered a good idiom. The smaller that the Rust language can be, the better. So if something can be implemented as a library, it's considered better to implement it as a library.

Rust doesn't even have (non-const) global variables, but you can use the lazy_static macro to create global variables. It also doesn't have an easy way to construct dictionaries, but you can use the maplit macros. Similarly, vec!, println!,assert!, panic!, matches!, and thread_local! are macros.

Macros are considered an integral part of Rust, and they are widely used for all sorts of things. They aren't a hack.

I personally would be in favor of built-in support for this, but the bar for new syntax and features is very high. So rather than new syntax, it's more likely that a clone! macro could be added to stdlib.

@burdges
Copy link

burdges commented May 2, 2020

I suspect our existing syntax provides the only intuitive choice:

let f = {
    let x = x.clone();
    let y = &y;
    move |w| ..x..y..z...;
};

There are no intuitive capture list proposals because they all increase language strangeness with new grouping operations.

We discussed clone || ... which clones any Clone captures, from which one recaptures normal behavior like:

let g = {
    let y = &y;  // Copy &T with &T: Clone  
    let z = NoClone(z);  // force move
    move |w| ..x..y..z...;
};

I believe clone || ... only makes learning the language harder because new users learn far more from reading f than reading g.

We also danced around some run_outside_this_closure! builtin that avoids capture lists, but the same critique applies. Instead I'd want NLL improvements so that if x: 'x then f: 'x + FnOnce() -> impl FnOnce() meaning the second closure relinquishes the lifetime 'x in

let f = || { let x = x.clone(); move || ..x.. }

There are good reasons for HashMap: Clone and ChaChaRng: Clone but also security implications because they involve secret randomness. If accidental clones become more endemic, then crate developers will respond by rejecting Clone for more types, which breaks #[derive(Clone)] and even demands crate forks. Ugh!

We already need an anti-clone lint for types like HashMap and ChaChaRng so that invoking HashMap::clone triggers a warning.

@kennytm
Copy link
Member

kennytm commented May 2, 2020

There's no way we would use the syntax clone |...| ..., as it is ambiguous with the | and || operators.

let clone = false;
let bool_var = true;
let res = clone || bool_var;

and we won't make clone a keyword either, as this either introduces some very specialized contextual rules (clone is a keyword before | and || but an identifier otherwise), or you need to use foo.r#clone() everywhere.

Forms including a keyword like move + clone |...| ... does work (though ugly).

@l4l
Copy link

l4l commented May 3, 2020

Maybe it is enough to use proc-macro with explicitly listed arguments instead of some syntax extensions? That approach already works pretty fine I think. Though, it is still requires stmt_expr_attributes and proc_macro_hygiene nightly features.

@burdges
Copy link

burdges commented May 6, 2020

I think move[..] |..| .. seems too niche, meaning it complicates reading and learning the language.

Instead, I'd suggest some external crate provide a "rebind" macro that avoids the repetition under more "self-like" situations, ala let foo = &foo.bar();, let foo = &mut foo.bar(); etc.

rb! {
    quiet_matched.clone();
    my_paths.iter().map(Path::to_path_buf).cloned().collect::<Vec<_>>();
    // Avoidt HashMap: Clone because it enables DoS attacks.
    my_hashmap.iter().map(|k,v| (k.clone(),v.clone())).collect::<HashMap<K,V>>()
    my_cow.into_owned();
    my_refcell.into_inner();
    my_mutex.into_inner();
    // I think Arc/Rc::clone/try_unwrap cannot work so easily.
};
Box::new(move |result| { ... })

We tie rb! closely to let foo = foo... which makes it less strange. It'd work for field puns in structs and enums too, which makes rb! more useful and slightly less strange.

@nightkr
Copy link

nightkr commented Feb 17, 2021

I think let v = *v; would do something different (e.g. dereference a String to str), but let v = {v}; (or just {v} if there’s only one use site) might be a way to force a move: https://bluss.github.io/rust/fun/2015/10/11/stuff-the-identity-function-does/

Bafflingly, only when v's type isn't Copy! This runs fine, but uncomment the #[derive(Copy)] and it all falls apart like a house of cards.

@alcjzk
Copy link

alcjzk commented Jan 12, 2022

How about having the compiler do the cloning when needed, by checking if the binding is used after having been moved into a closure? The behaviour could be gated with a keyword like cloneused/autoclone or such to make the behaviour both optional and more transparent.

edit: example syntax

let foo = SomeCloneable::new();
thread::spawn(move, cloneused || {
  let bar = foo; // foo is cloned due to being used later in it's original scope
});
let baz = foo; 

@SoniEx2
Copy link

SoniEx2 commented Jan 13, 2022

It would be nice to just have movable bindings.

let foo = SomeCloneable::new();
let move foo = foo.clone();
thread::spawn(move || {
  let bar = foo;
});
let baz = foo;

instead of

let foo = SomeCloneable::new();
{
  let foo = foo.clone();
  thread::spawn(move || {
    let bar = foo;
  });
}
let baz = foo;

@jgarvin
Copy link

jgarvin commented Jan 14, 2022

The precedent is already set that we change how arguments are captured by putting something in front of the lambda, move.

If explicit is generally better than implicit, isn't it clearer to allow people to explicitly specify which things they want moved? move(a,b,c) |arg| { ... }. Then we would only have the question of how to specify behaviors other than move. For a systems programming language, it seems weird I have no defense against accidentally bloating my closures with more captures. The closest you can get I'm aware of is putting the closure in a temporary so you can then assert size_of on it, which can then break due to types changing sizes on diff platforms or Rust ABI changes.

@SimonSapin
Copy link
Contributor

Variable-specific syntax was discussed in https://rust-lang.github.io/rfcs/0114-closures.html#alternatives

Use a capture clause syntax that borrows individual variables. "By value" closures combined with let statements already serve this role. Simply specifying "by-reference closure" also gives us room to continue improving inference in the future in a backwards compatible way. Moreover, the syntactic space around closures expressions is extremely constrained and we were unable to find a satisfactory syntax, particularly when combined with self-type annotations. Finally, if we decide we do want the ability to have "mostly by-value" closures, we can easily extend the current syntax by writing something like (ref x, ref mut y) || ... etc.

@jkelleyrtp
Copy link

jkelleyrtp commented Feb 14, 2022

I find that this issue comes up very very frequently, especially in UI land, and is the number one thing holding back Rust GUI libraries, and Rust GUI in general.

People come from other ecosystems typically looking to build a GUI in this new language they just learned. GUIs and games are the most visual way of seeing your code come to life. The Yew, Dioxus, Sycamore, GTK, Iced, Druid libraries all struggle with teaching beginners about why this is necessary in the first place.

This issue is personally my #1 problem with Rust.

My proposal:

A new trait like Copy that takes advantage of the Copy machinery under a new capture keyword. Perhaps this trait could be CheapClone or Capture.

Right now, if you want a value to be moved into a closure with no ceremony, it must implement the Copy trait. This is an explicit action you must take as a library developer to make your types work well in closures. This is what we do in Dioxus. Copy is a trait that signifies a value can be copied by bytes and typically is a marker that this item is "cheap" to work with.

Likewise, a CheapClone or Capture trait would signify that this type is cheap to Clone. To implement the trait, we would simply defer to the clone implementation. In the Rust standard library, it would only be implemented for Rc and Arc.

trait Capture {
    fn capture(&self) -> Self;
}

impl<T> Capture for Rc<T> { // and Arc
    fn capture(&self) -> Self {
        self.clone()
    }
}

Then, to take advantage of Capture, we would replace our uses of move with capture. The capture keyword is the exact same as move but with the added benefit that any types that are not normally copy can be captured using capture provided they implement the Capture trait. By default, on Rc and Arc are Capture, but developers can make any value of theirs Capture by deriving or implementing the Capture trait.

Using Capture would look like move:

let val = Rc::new(thing);
let handler = capture |_| println!("{val}");

This is especially helpful for the many async bits of the ecosystem:

fn app(cx: Scope) -> Element {
    let val = use_ref(&cx, || None);

    // What we have to do today
    cx.spawn({
        let val = val.clone();
        async move {
            val.set(Some("thing"));    
        }
    });

    // What the future could be
    cx.spawn(async capture {
        val.set(Some("thing"));
    });
}

I personally don't see Capture any differently than Copy - teaching capture would be the same, their implementations would be similar, but the benefit to Rust would be huge (IMO).

@Diggsey
Copy link
Contributor

Diggsey commented Feb 14, 2022

The capture keyword is the exact same as move but with the added benefit that any types that are not normally copy can be captured using capture provided they implement the Capture trait.

But types that are not copy can already be captured by move. If you mean that types implementing Capture will be cloned instead of moved, then that would be an unprecedented change in behaviour based on whether a trait is implemented or not: normally implementing a trait only makes more things compile it doesn't change the behaviour of code that used to already compile.

I personally don't see Capture any differently than Copy - teaching capture would be the same, their implementations would be similar, but the benefit to Rust would be huge (IMO).

Copy has a clear definition, whereas Capture seems pretty arbitrary. How cheap is "cheap"? Why even have Capture at all if we have Clone?

What if I have two things implementing Capture, and I want one of them to be cloned into the closure, and the other one moved?

@jkelleyrtp
Copy link

jkelleyrtp commented Feb 14, 2022

The capture keyword is the exact same as move but with the added benefit that any types that are not normally copy can be captured using capture provided they implement the Capture trait.

But types that are not copy can already be captured by move. If you mean that types implementing Capture will be cloned instead of moved, then that would be an unprecedented change in behaviour based on whether a trait is implemented or not: normally implementing a trait only makes more things compile it doesn't change the behaviour of code that used to already compile.

If a type cannot be moved into the closure because it is used in the enclosing scope, then capture would kick in. move would take precedence over capture unless there are other borrows in scope. This is not too dissimilar to the recently added partial borrowing behavior.

I personally don't see Capture any differently than Copy - teaching capture would be the same, their implementations would be similar, but the benefit to Rust would be huge (IMO).

Copy has a clear definition, whereas Capture seems pretty arbitrary. How cheap is "cheap"? Why even have Capture at all if we have Clone?

Capture is whatever the implementor wants capture to be. Many other traits are arbitrary - notably Display Debug don't put any requirements on how the trait must be implemented. Just that you can call their Debug/Display implementations that must satisfy the trait.

The recommendation should be that types that are cheaply cloneable are capture. For std, only Arc/Rc satisfy this criteria. By default, all Copy is Capture. Library implementors would be free to implement this however they seem fit.

What if I have two things implementing Capture, and I want one of them to be cloned into the closure, and the other one moved?

Because move takes precedence over capture, the type that can be moved will be moved and the type that can be captured will be captured. If you need to take a manual clone, you still can, since the cloned item will be handled by move anyways.

IMO Rust is in a weird position where we don't have a garbage collector, so it's taught that you should use RC when you don't have a clear owner of data. But, there is close to zero language support for RC and is made especially apparent in GUI land. In many ways, std acts like RC doesn't even exist, even though a large amount of programs (and many/most larger libraries) use it extensively.

@nightkr
Copy link

nightkr commented Feb 14, 2022

If a type cannot be moved into the closure because it is used in the enclosing scope, then capture would kick in. move would take precedence over capture unless there are other borrows in scope.

I don't believe the borrow checker is currently allowed to affect behaviour, only to reject otherwise-working programs. For example, mrustc skips borrow checking entirely, on the assumption that the code has already been validated by rustc.

Invisibly invoking (an equivalent of) Clone::clone, especially based on hidden conditions that are not particularly obvious, seems like a dangerous road to start walking down.

By comparison, partial borrowing is more or less "just" a precedence change, changing the interpretation of &x.y.z from (&x).y.z to &(x.y.z).

@jkelleyrtp
Copy link

jkelleyrtp commented Feb 14, 2022

If a type cannot be moved into the closure because it is used in the enclosing scope, then capture would kick in. move would take precedence over capture unless there are other borrows in scope.

I don't believe the borrow checker is currently allowed to affect behaviour, only to reject otherwise-working programs. For example, mrustc skips borrow checking entirely, on the assumption that the code has already been validated by rustc.

Invisibly invoking (an equivalent of) Clone::clone, especially based on hidden conditions that are not particularly obvious, seems like a dangerous road to start walking down.

Copy does pretty much the same thing already.

For example, this array is "silently" copied into two handlers. In fact, since it's Copy it's also Clone. We're calling Clone silently! What if this array was 100,000 elements large? You need to know that the type is Copy for it to succeed.

fn main() {
    let vals = [0; 10];
    let handler1 = move || println!("{vals:?}");
    let handler2 = move || println!("{vals:?}");

    handler2();
    handler1();
}

The Capture trait would just toss some sugar on top of cheap clones to do exactly the same optimization, but for RC types.

fn main() {
    let vals = std::rc::Rc::new([0; 10]);
    let handler1 = capture || println!("{vals:?}");
    let handler2 = capture || println!("{vals:?}");

    handler2();
    handler1();    
}

I don't think the conditions are any more hidden than the fact the Copy gets copied on move. In fact, I think capture is more obvious because it's a trait dedicated for this very purpose, rather than Copy somehow interacting with the move keyword.

Additionally, nowhere in the Copy docs does it mention the behavior that Copy types are copied using the move keyword.

https://doc.rust-lang.org/std/marker/trait.Copy.html

In fact, since move has its own special behavior with Copy types, I don't see why Capture couldn't also interact with move in the exact same way.

EDIT:

I've created a thread at https://internals.rust-lang.org/t/a-capture-trait-for-cheaply-cloning-into-closures/16141

@jplatte
Copy link
Contributor

jplatte commented Feb 14, 2022

A lot of people are subscribed here. I think it is a good place to link to (discussions about) specific proposals, but not a good place to discuss individual proposals. @jkelleyrtp I suggest creating a thread on https://internals.rust-lang.org/.

@RSSchermer
Copy link

RSSchermer commented Mar 29, 2022

Here's another idea I wanted to throw out there for consideration:

Perhaps this problem could be alleviated by adding an autoclone marker keyword for variables analogous to the mut marker, that would instruct to compiler to automatically call clone for us when it needs to, such as when moved into a closure or when passed to a function that takes ownership:

let autoclone foo = Foo::new(...); // Must implement `Clone`, otherwise error
thread::spawn(move || { // Clones `foo`
    foo.baz();
});
takes_ownership(foo); // Clones `foo`
let bar = foo; // End of `foo`'s NLL-scope (it's never used again), no clone required: moves `foo`

The autoclone marker would be allowed in all the same places the mut marker is currently allowed. Like mut, it would apply only to the variable until the end of that variable's scope. It would not be inherited by downstream variables (e.g. bar in the example above is not marked autoclone, the compiler is not allowed to automatically clone bar). It would be allowed along-side the mut marker, e.g.:

let autoclone mut foo = Foo::new(..);

This does introduce more magic. Especially in longer function bodies, one might come across the use of a variable foo that is not obviously autoclone as its declaration happened way earlier in the code. I think this is alleviated by the fact that the effect of autoclone is always limited to a single lexical scope, that is to say, there is only ever one place someone would have to look to determine if a foo variable is autoclone, which is the declaration site for foo. I would also argue that variables that hold Copy values come with a similar (identical?) problem, which could be taken as precedent. One might say that the autoclone marker essentially tells the compiler: treat this variable as if it were Copy (if it isn't copy already), except you must explicitly call the Clone implementation.

Some further considerations:

  • Perhaps disallow the autoclone marker for values that are also Copy, to prevent ambiguity in what happens on a move (e.g. let autoclone foo = Foo::new(); errors if Foo implements Copy).
  • Perhaps an explicit ordering should be defined when autoclone is used alongside mut, e.g. "autoclone must always occur first" (let autoclone mut foo = ...), or "mut must always occur first" (let mut autoclone foo =...).
  • "autoclone" is perhaps a bit long for a keyword, maybe someone can come up with a shorter label that conveys a similar meaning.

Discussion topic on internals.rust-lang.org.

@mitinarseny
Copy link

I'm new to Rust and I don't know if this is related, but I think it has something to do with the topic of the current discussion and may bring new ideas. Correct me if I'm wrong.

I am trying to implement a function, which takes IntoIterator of paths to directories, where IntoIterator::Item is AsRef<Path>. This function should return another iterator of all *.desktop files found in given directories (I wrap them into some struct DesktopFile). So, the inner part creates a new WalkDir, maps and filters it's items and then flattens the resulting iterator, since the user only wants to iterate over files, regardless of where this file was found.

The only solution which I came up with is {let x = x.clone(); move || some_fn(&x)}:

pub fn desktop_files<I>(dirs: I) -> impl Iterator<Item = DesktopFile>
where
    I: IntoIterator,
    I::Item: AsRef<Path>,
{
    dirs.into_iter()
        .map(|d| {
            WalkDir::new(&d)
                .min_depth(1)
                .follow_links(true)
                .into_iter()
                .filter_map(|entry| entry.ok())
                .filter(|entry| entry.metadata().ok().map_or(false, |md| md.is_file()))
                .map(|entry| entry.into_path())
                .filter(|p| p.extension().map_or(false, |ext| ext == "desktop"))
                .filter_map({
                    let d = d.as_ref().to_owned();
                    move |p| {
                        p.strip_prefix(&d).ok().map({
                            let d = d.clone();
                            move |sp| DesktopFile::new(d, sp)
                        })
                    }
                })
        })
        .flatten()
        .unique_by(|df| df.id())
}

@kang-sw
Copy link

kang-sw commented Mar 14, 2023

What about borrowing c++ syntax a bit?

let (foo, bar, baz) = (1,2,3);
let closure = [foo, &bar, =baz] || {}; // move, reference, clone respectively
let task = async [&, mut foo, =mut baz] { }; // fallback capture = reference

Just kidding :|


Quickly created a crate from above syntax. Applied weird syntax on declaring mutable variables, which makes rustfmt understand macro expression as valid rust code.

crate

        let ss = std::rc::Rc::new(());
        let [foo, bar, baz, mut qux] = std::array::from_fn(|_| ss.clone());

        let _ = capture!(
            [foo, *bar, &baz, &mut qux, cloned = bar.clone(), *other = cloned.clone(), oar = 3],
            || {
                // foo, mut bar -> cloned
                // cloned, mut other, oar -> created
                // &baz, &mut qux -> referenced
                // other unspecified variables -> moved
            }
        );

@Awpteamoose
Copy link

Awpteamoose commented Aug 16, 2023

I also made a crate: https://crates.io/crates/clown

let foo = Arc::new(...);
thread::spawn(#[clown] || {
    honk!(foo).baz();
});
foo.baz();

expands to

let foo = Arc::new(...);
thread::spawn({
    let __honk_0 = (foo).clone();
    move || {
        __honk_0.baz();
    }
});
foo.baz();

pros compared to function-like approaches e.g. https://crates.io/crates/enclose

  • no extra parens
  • doesn't confuse tooling (highlights, etc)
  • don't need to specify all captures upfront so it's easier to refactor
  • doesn't add bloat in front of lambda arguments

cons:

  • nightly features
  • honk! doesn't work in macros (altho that should be solvable if I just parse raw tokens instead of relying on syn visitor) just released 1.1.0 and it works in macros just fine

@Niedzwiedzw
Copy link

Niedzwiedzw commented Aug 26, 2023

here's my take on it:

I'm using build.rs to generate bunch of extension traits for tuples, only caveat is that method names are suffixed with _0, _1, ..., depending on number of arguments target closure takes... on the other hand this has the advantage of not breaking rustfmt

https://github.com/Niedzwiedzw/to-cloning-closure/blob/master/src/generated.rs

usage:

    #[test]
    fn test_it_works() {
        use to_cloning_closure::prelude::*;

        let append_index_to = ("whatever".to_owned(),)
            .with_cloned_1(|(whatever,), idx| format!("{whatever} - {idx}"));
            
        let _ = (0..2137).map(append_index_to).collect::<Vec<String>>();
    }

@tema3210
Copy link

tema3210 commented Sep 25, 2023

What if we go in a different direction? Like, we can specify, how to capture something inside of a closure:

|arg| {
   let result = job(capture1 by Clone::clone)
}

Or generally anything that has signature fn(&self) -> Self, like Rc::clone, etc.

for move closures it allows to precisely override the default behavior and has no surprises since those closures are already impl FnOnce.

Also we can allow syntax capture2 by move to allow closures to move in part of their captives without resorting to tricks.

Which, however, keyword to pick? and what's the value?

Edit: also may be quite cryptic aka |arg| arg.do_stuff(text by Clone::clone)

Edit 2: What's about ... [Clone::clone]capture syntax?

Unresolved: It feels strange when such a thing is used as expression (from UX perspective): is it a one time capture or change of capture mode for a binding in scope?

@SOF3
Copy link

SOF3 commented Sep 28, 2023

@tema3210 That feels as unsound as a language structure that runs code in front of a function and assigns their results to variables (like defer in Go but opposite).

@SOF3
Copy link

SOF3 commented Sep 28, 2023

Actually @jgarvin's suggestion above sounds like the right compromise. We could just extend the current move syntax for backwards compatibility:

|| uses(a) // borrows `a` mutably or immutably depending on requirement
move || uses(a) // moves `a` into closure
move(a) || uses(a) // same as `move || a`
move(&a) || uses(a) // same as `|| uses(a)`
move(a=&a) || uses(a) // same as `move(&a) || uses(a)`; `&a` and `&mut a` are specifically defined to be `a=`
move(a = a.clone()) || uses(a) // same as `{let a = a.clone(); move || a}`
move(a.clone())` // same as the last line, since this is an expression that only involves one local variable

But in that case, the move() syntax here is more like a multi-let operator that isn't even specific to closures:

"move" "(" (ASSIGNMENT ","?)+ ")" EXPR
ASSIGNMENT := (PATTERN "=")? EXPR

where the last EXPR can use bindings defined in PATTERNs.

@tema3210
Copy link

tema3210 commented Oct 8, 2023

@tema3210 That feels as unsound as a language structure that runs code in front of a function and assigns their results to variables (like defer in Go but opposite).

It is structure that customizes how a capture is made, that's it. Capture is a known concept after all.

The UX however gets very weird very quick:

|| { job([Clone::clone] captive) ;job(captive) } - what second mention of captive refers to? - the original binding from closure env, or the clone in closure state?

  • what happens for &[Clone::clone] captive and [Clone::clone] &captive? - I'd suggest we don't do autoref for the callable in brackets, so the first is incorrect and second clones into closure captures.

@ranile
Copy link

ranile commented Oct 8, 2023

I really like @SOF3's suggestion. It's backwards compatible and ties neatly with the current syntax. I would like to see a formal RFC for it. If no one else wants to write it, I'd be happy to write one and see where it goes. As it stands, I don't think we're getting anywhere with discussion in this issue

@SOF3
Copy link

SOF3 commented Oct 9, 2023

I would like to try writing the RFC here

@BaZzz01010101
Copy link

Actually @jgarvin's suggestion above sounds like the right compromise. We could just extend the current move syntax for backwards compatibility:

|| uses(a) // borrows `a` mutably or immutably depending on requirement
move || uses(a) // moves `a` into closure
move(a) || uses(a) // same as `move || a`
move(&a) || uses(a) // same as `|| uses(a)`
move(a=&a) || uses(a) // same as `move(&a) || uses(a)`; `&a` and `&mut a` are specifically defined to be `a=`
move(a = a.clone()) || uses(a) // same as `{let a = a.clone(); move || a}`
move(a.clone())` // same as the last line, since this is an expression that only involves one local variable

But in that case, the move() syntax here is more like a multi-let operator that isn't even specific to closures:

"move" "(" (ASSIGNMENT ","?)+ ")" EXPR
ASSIGNMENT := (PATTERN "=")? EXPR

where the last EXPR can use bindings defined in PATTERNs.

I thought about very similar thing while was reading the thread. Just with additional "clone" keyword for cloning.

clone || uses(a, b, c) // clone all variables that implements Clone and use by ref others
clone move || uses(a, b, c) // clone all variables that implements Clone and moves others
clone(a) || uses(a, b, c) // clone 'a', by ref 'b' and 'c'
clone(a) move(b) || uses(a, b, c) // clone 'a', move 'b', by ref 'c'

But I'm suspecting that adding new keyword is a very breaking change. Especially so common like 'clone'...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
T-lang Relevant to the language team, which will review and decide on the RFC.
Projects
None yet
Development

No branches or pull requests