Why does the below code work ? I thought with move keyword, movable is owned by the closure, so it becomes FnOnce. So how is the 2nd call to consume() not throwing compiler error? Thanks.
fn main() {
let movable = Box::new(3);
let consume = move || {
println!("movable: {:?}", movable)
};
Since the closure bound to consume doesn't do anything that requires moving out of movable, it continues to own movable until the closure itself is dropped. The function implements the Fn trait, as a result, and can be called multiple times.
If, on the other hand, the body of that closure moves out of movable, or drops it explicitly, then the closure must implement FnOnce, and can only be called once. For example, this doesn't compile:
Ah, thank you. My misunderstanding was that with the 'move' keyword, the enclosing value will be owned by the closure and hence that closure can be called only once.
@derspiny - If I can ask one more question on the topic,
At the outset, it looks like the Rust compiler, by analysing the code inside the closure, can use the enclosing values in the least restrictive manner. As a Rust beginner, it appears like the compiler could live without the 'move' keyword altogether. So what is the real purpose of the move keyword, then? In my example above, the code runs with or without the move keyword. Thanks.
That's correct, this is exactly what this keyword is for - to make closure own everything it captures.
That's wrong. If you own some value, you can do anything non-moving with it (e.g. printing) any number of times. Therefore the closure can too.
Note that there are two "manners" of "restriction" here.
One is "what can the previous owner do with the captured value?" This is what different capture modes (and move keyword) is about:
when something is captured by shared reference, for example, owner can make other shared references to the same value as it wishes;
when capture is by unique (mutable) reference, value is locked as long as the closure exists, but is again under full owner's control after closure is dropped;
when capture is by move, previous owner loses the value entirely.
Here, Rust indeed does the least restrictive thing, as long as we don't force it to move everything.
The second restriction, however, is on what we can do with closure itself. And here the restriction is actually reversed:
when closure captures something by reference, it must stay alive no more than the scope of that reference, i.e. if it captures a local variable - it can't be returned to the caller or passed to another (non-scoped) thread;
when closure captures everything by value, it can be moved independently, just like the values themselves.
And move keyword is necessary to force the second variant when, from the closure's point of view, the first would suffice, but caller needs it to be used in the incompatible way.
...and depending on how the captured values are used in the closure body, implements some of the Fn/FnMut/FnOnce traits...
// n.b. this is a slight simplification of the actual traits
impl FnOnce<(Arg, Types)> for Closure<'_, '_> {
type Output = OutputType;
fn call_once(self) -> Self::OutputType { ... }
// ^^^^
}
impl FnMut<(Arg, Types)> for Closure<'_, '_> {
fn call_mut(&mut self) -> Self::OutputType { ... }
// ^^^^^^^^^
}
impl Fn<(Arg, Types)> for Closure<'_, '_> {
fn call(&self) -> Self::OutputType { ... }
// ^^^^^
}
...and perhaps some other traits like Clone if possible.
What happens when you call the closure? It will call the most lenient implementation possible. You'll only call FnOnce::call_once and give up ownership of the closure -- so that it cannot be called again[1] -- if you "have" to:
Because the closure gives up ownership of some non-Copy field that it captured
let s1 = "hello".to_string();
let s2 = "world".to_string();
let closure = || {
println!("{s1}, {s2}!");
// The closure erturns `s1`, so it must
// - take `s1` by value so it has the ability to give it away at all
// - give up ownership of `s1` after it is called
// - i.e. it can only implement `FnOnce`, not `FnMut` or `Fn`
s1
};
// let _ = &s1; // error, it has been moved into the closure
let _ = &s2; // This is fine, `s2` was only captured by shared reference
let _s = closure();
let _s = closure(); // error, it can't give you `s1` again
Because you're in a generic setting that only has a FnOnce bound
fn example<F: FnOnce()>(closure: F) {
closure();
closure(); // error, your only option was to call `FnOnce::call_once`
}