fn main() {
// `value` moved, why `mut` needed?
let mut value = 0;
// removing `mut` doesn't compile.
let mut closure = move || {
value += 1;
println!("{}", value);
};
closure(); // 1
closure(); // 2
println!("{}", value); // 0
}
I am no expert but probably because in the context that it is moved to it is mutated.
Because there's no other place to put mut
on, and value
binding is mutated.
Calling this closure mutates its captured environment, so calling it requires the FnMut trait, which takes a mutable reference to the closure itself. (The environment is stored within the closure value.)
That explains why closure
needs to be marked mutable, but not why value
needs to be marked mutable. But @kornel already answered that question.
Also see https://users.rust-lang.org/t/mut-binding-required-to-mutate-move-captured-var-in-a-closure/26825
I agree that requiring that the captured variable be mut
makes no sense, although mut
bindings in general are just a glorified-by-the-language lint, so it being required in a place where it should not is not that bad. But definitely an inconsitency of the language.
The rule should be that variables captured by a closure are always mut
-bound, following anonymous bindings rules:
fn main ()
{
let x = ::scopeguard::guard(27, |x| { dbg!(x); });
*{x} += 42; // anonymous binding in `{x}` expression
}
That being said, it would remove the possibility for the programmer to state when captured variables / bindings should not be mutated1.
So it ends up being a case where the inconsistency gives programmers more "expressiveness", leading to a "it could be worse" accepted status quo.
1 at least for a FnMut
closure: in the case of a FnOnce
closure, bindings can be rebound within the body of the functionn so their mut
-lint-ability can be changed / specified there.
Only the value
binding captured by the closure is mutated. The value
binding in the top-level main
scope is not, as the last println!
shows. After all, the following equivalent code does compile.
fn main() {
let value = 0;
let mut closure = {
// Makes a mutable copy for the closure to capture.
let mut value = value;
move || {
value += 1;
println!("{}", value);
}
};
closure(); // 1
closure(); // 2
println!("{}", value); // 0
}
In the end, it can be argued both ways and I don't know which one is the least surprising.
They are the same binding. The error is not that mut
is required, but that the binding is still in scope. The fact that the value is Copy
shouldn't matter as the binding is really what is being moved here.
So the code in the OP should not compile. The only way I see to fix this is to add a lint against it for now and consider forbidding it in the next edition.
I don't think so. It is the defining feature of Copy
that bindings remain usable after being moved from.
Here is yet another equivalent code, desugaring the FnMut
closure to a struct
and an explicit &mut self
method. In this case, no mut
is needed on the value
binding.
struct Closure(i32);
impl Closure {
fn call(&mut self) {
self.0 += 1;
println!("{}", self.0);
}
}
fn main() {
let value = 0;
let mut closure = Closure(value);
closure.call(); // 1
closure.call(); // 2
println!("{}", value); // 0
}
Please reread what I said carefully as I was very carefully in choosing my words. I'm not talking about a value being moved out a binding, I'm talking about a binding being moved out of a scope.
Also none of you code examples are equivalent because they all create new bindings. Yes they do compile to the same thing, but I'm talking about how theses read to a human. This is a ugly corner case and I only see one why to fixed.
I,as a human,interpreted it as being "moves ownership of the value into the closure",and am confused how bindings are a thing that could be moved(that is a concept I haven't seen in any language I looked at).
@matt1985 In every language with closures, the closure captures the binding, not the value. That's why mutating value
causes the outer value
to change, and those changes persist even between closure calls. This is very standard.
The only unusual thing here is that it's still possible to access value
after it has been moved into the closure. That's a side effect of Rust's Copy
semantics.
Well,generally how I think about closures in garbage collected languages is that they have a reference to the stack frame where those variables were defined,allowing them to access all those variables even after the function returns. I know that this model is probably not accurate since that's not how stack frames work,and that it would keep a lot more objects around than necessary,but "bindings" never enter into the equation for me.
As a bit of rust intuition then, in general when you wind up with two mut
s, they are necessarily two different things. How you got there can be a riddle though.
@matt1985 There's many ways to implement closures. But when you're thinking about language concepts, you shouldn't be thinking about the implementation details (which can change), you should be thinking about them at a high level.
From a high level, regardless of the implementation, closures capture bindings. This is proven through how closures actually behave:
When you call the make
function, it creates a local value
binding and then it returns a closure. This value
binding is captured (in this case moved) into the closure. So whenever you call the closure, it will update that binding.
And as you can see, every time you call make
it creates a distinct value
binding, so foo
and bar
use different bindings for value
.
It's important to note that bindings are not syntactic. If you look at the syntax you would think that foo
and bar
have the same value
binding, since in the syntax there is only one let mut value = 0
, but that's not what's actually happening.
Instead you get a new value
binding every time make
is called. But you don't get a new binding every time foo
or bar
is called, because it reuses the same value
binding it had when it was created.
As a side note, Rust implements the above code similar to this. All captured bindings get passed into a struct, and the closure body gets turned into a method.
It gets even more interesting when the closure doesn't move the bindings. In that case, this code gets translated into something similar to this code. In other words, the closure captures a &mut
reference to the binding, rather than moving the binding into the closure.
But from the programmer's perspective, the closure captures the value
binding, and the exact way that Rust implements that isn't too important.
I don't know why you felt the need to explain closures to me.
I just think that disallowing the use variables of Copy types after they were were used inside a move closure is unnecessarily restrictive. Eg:
let a=0_i32;
let func=move||drop(a);
println!("{}", a); // For some reason this wouldn't compile???
The previous code exemplifies my problem with the talk of "bindings that are moved",a concept I find totally counter-intuitive.
As an example for why bindings aren't ever moved here is this:
let mut a=String::from("hello");
let func=move||a;
a=String::from("world");
println!("{}",a);
println!("{}",func());
if the binding(rather than its value) was the thing that moved,then it wouldn't even be a valid name to refer to after the closure was constructed.
Essentially,when you use move closures,they create implicit bindings that the values of the variables it refer to get moved into.
@matt1985 However, in this code you cannot use a
after it is moved into the closure.
Your example only works because it is assigning to a
, and NLL is smart enough to realize that you are not able to access the old value of a
. This is easy to prove by slightly modifying your code. I'm curious whether your code would compile without NLL.
So, perhaps before NLL it was correct to say that bindings are moved into closures, but NLL has changed that. Very interesting. So it seems Rust has now deviated from how other languages treat captured bindings.
That code compiles in Rust 1.0 Compiler Explorer.
I don't understand what lifetimes have to do with it,since the closure doesn't borrow the variable,it moves its value into an implicitly created binding only usable in the closure(that's how I model what's happening).
Ìf you look at:
fn main() {
let mut a=String::from("hello");
let mut func=move|b:&str|{
a=String::from("foo");
assert_eq!(a,"foo");
assert_eq!(b,"bar");
};
a=String::from("bar");
func(&a);
}
The a
inside and outside the closure are different variables of the same name.
@matt1985 Hmm, I must have confused it with another aspect of NLL. Thanks!
This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.