I dont think that making cloning easier is a generally good, as the keyword can be abused to just get rid of the errors, but i believe it has some use cases where the benefit would outweigh bad practice usage.
Im not a compiler dev, but i think that the implementation of the keyword can be done exactly as the move, just change the requirement for the Copy trait to require Clone instead and add a .clone() call to each usage of a variable captured from the outside.
Thank you for the read, if you know of a better way to do this please share it with me.
You're cloning things twice here, I imagine it isn't relevant to the example though. (is the second set of clones there on accident?)
I think this introduces a very easy footgun where you write a clone || knowing what exactly gets cloned, being fine with it. Later you'd modify the code such that it inadvertently clones something else, possibly something very expensive to clone.
That outside item would get cloned, and you wouldn't be loudly notified with some sort of use after move compile-time error like you would with a move ||. Instead, the program would run (likely as intended) just with the added cost of that clone which you have no idea is happening. It makes certain clones implicit, and that's the job of Copy.
Note, did you read the last articles by Niko Matsakis on this subject? I think something similar to your proposal is discussed, among many other variations
Cloning twice is a thing! (or rather cloning N+1 times)
You first clone outside the closure because you can neither move nor borrow, then you clone inside the closure because the closure will run multiple times, and each time it runs you want to increase a reference count or something (in case inside the closure you want to store that in a data structure or send through a channel etc, every one will keep a reference to the original data, and the closure itself will keep a reference as well, until it's dropped - hence N+1 clones)
In the last Niko article about this, Move Expressions ยท baby steps he has an example of that using the move(..) proposal (which I think looks better spelled move { .. } but anyway)
data_source_iter
.inspect(|item| {
inspect_item(item, move(tx.clone()).clone())
// ---------- -------
// | |
// move a clone |
// into the closure |
// |
// clone the clone
// on each iteration
})
.collect();
// some code that uses `tx` later...
The interesting about this syntax is that most of time I definitely want to see two clones spelled out in the code. Well except if this pattern happen in clone-heavy code like UI or some other stuff, then it's just line noise.
So maybe there should be a keyword that allows me to omit the clones (or .alias()/.handle()/.use/whatever). So indeed async clone || .. seems very nice for the use cases it makes sense
What about nested closures where the inner closure clones or moves something from the outside of the innermost closure? What if the outer closure is FnOnce and the inner FnMut (or vice versa, both seem complicated and different)?
Because of these sorts of ambiguous situations, I prefer what C++ is doing for the capture lists there. One of the few things C++ got more right than Rust.
But what means clone(sender)? Is this sugar for Niko's move(sender.clone())? If yes, I'm not sure but I think it will clone twice, and if you don't like that you should clone only once
something(|| {
let channel = clone(sender);
channel.send(123);
channel.send(456);
});
Well, it looks like adding capture lists to Rust closures is on the table.
Yes, sorry, I have seen so many variants proposed in different places that it is hard to remember which is which. The issue applies to all variants which doesn't have a capture list up front, but rather embedded into the body. (That is also true for the nested closures issue.)
Actually, thinking about it, it's nice that move(..) and drop(..) look alike. It should maybe be spelled move!(..) though, since there is no way this can actually be a function
Postfix operator syntax is good for sequential code that evaluates an expression normally and then does something special with the result, and .await and ? both work like that. But this move(...) operator causes the evaluation to happen earlier (when the closure is constructed), is a thing that really should not be possible to hide with postfix syntax.
|| {
let thing = Thing::builder()
.foo()
.bar()
.build()
.unwrap()
.move; // Surprise! All that code actually ran earlier.
}
(Niko wrote about this briefly under the heading โWhy not suffix?โ)
(On second thought, implicit cloning wouldn't always work since not all types implement Clone. How would types that don't implement Clone fit into this?)