In Chapter 13.1, we have the text:
FnOncetakes ownership of the variables it captures from the environment and moves those variables into the closure when the closure is defined. Therefore, aFnOnceclosure cannot be called more than once in the same context.
The natural reading of this (as I read it, as a learner) is that you can make the FnOnce closure, and afterward, you can only call it once. This seems crazy as saving the context as a piece of a closure and preserving it for reuse is one of the main uses of a closure. But it's also verifiably false:
fn foo() {
let x = "bar".to_string();
let f = move || { x.to_uppercase() };
f();
f();
}
The above compiles. If we append let g = || { x.to_lowercase(); }; we see that x has indeed moved into f, because we can't use it in g.
I'm not sure quite what the paragraph was intended to say, or I would submit a PR. Also I'm a rust learner, not an expert! But this seems worth altering.
The closure in your example does not actually implement FnOnce. An actual FnOnce closure, such as move || x (where x has a non-Copy type), consumes itself when called, and therefore cannot be called more than once.
Then there is something funny about those three paragraphs -- this takes ownership of x, but isn't a FnOnce, so must be a Fn or a FnMut. Yet those other two paragraphs say Fn and FnMut only borrow things from their environment, not actually take ownership.
Ok, the wording is correct but misleading; the environment here is not the surrounding scope, it's the value that is bound to the self of a closure.
move of a value into a closure`s environment happens before the closure runs; the exact trait the closure can implement depends on how the closure uses the environment when it runs.
In your example, imagine trying to implement your closure using regular struct and impl; you'd get something like:
struct Closure {
x: String
}
impl Closure {
pub fn new(x: String) -> Self {
Closure{ x }
}
// self here is the closure's environment
// we have call by ref; i.e. a Fn
pub fn call(&self) -> String {
self.x.to_uppercase()
}
}
fn main() {
let x = "bar".to_string();
// now the closure `move` the value into its environment
let c = Closure::new(x);
println!("Calling once: {}", c.call());
println!("Calling twice: {}", c.call());
}
As you can see, when the closure runs it does not need to consume self; it would be another matter if the method was
pub fn call(self) -> String {
self.x
}
Here the call method has to consume self because we move the value bound to self.x.
Your explanation makes perfect sense, thank you! I wonder if the book can be adjusted to avoid the word "environment" then, which (as you say) is a bit misleading?
If the word "environment" is standard here then please ignore this of course; it was new to me.
So "environment" is a standard term having to do with closures, but I personally have problems understanding/remembering/using it precisely. I'm not sure if we're using it correctly and in a clear way here or not.
From wikipedia:
Operationally, a closure is a record storing a function together with an environment: a mapping associating each free variable of the function (variables that are used locally, but defined in an enclosing scope) with the value or reference to which the name was bound when the closure was created.
From mdn:
A closure is the combination of a function and the lexical environment within which that function was declared.
My head hurts.
@steveklabnik would it be more accurate or less accurate if I change:
FnOncetakes ownership of the variables it captures from the environment and moves those variables into the closure when the closure is defined.
to:
FnOncetakes ownership of the variables it captures from its enclosing scope and moves those variables into the closure when the closure is defined.
?
It would not, though for people who do understand "environment", it might be worse.
What about
FnOnce takes ownership of the variables it captures from its enclosing scope (a.k.a. "its environment") and moves those variables into the closure when the closure is defined.
The problem with such a explanation is that it focuses on the time of variable capture instead of the time of closure execution, which is when the actual difference between the various closure Traits matters.
What about
FnOnce consumes the variables it captures from its enclosing scope (a.k.a. "its environment") ; it must therefore take ownership of these variables and moves them into the closure when it is defined.
I've been bitten by this as well, and got stuck on
Therefore, a FnOnce closure cannot be called more than once in the same context.
This thread explanation make sens, and I hope a longer explanation can be but in the book.
The latest proposed phrasing make more sens to me:
FnOnce consumes the variables it captures from its enclosing scope (a.k.a. "its environment") ; it must therefore take ownership of these variables and moves them into the closure when it is defined.
It does convey that FnOnce is the consequence of what the closure does – returning a value, that therefore have to be moved into the closure – and not the cause. I think that would be a way better explanation in the book.
Most helpful comment
Ok, the wording is correct but misleading; the environment here is not the surrounding scope, it's the value that is bound to the
selfof a closure.moveof a value into a closure`s environment happens before the closure runs; the exact trait the closure can implement depends on how the closure uses the environment when it runs.In your example, imagine trying to implement your closure using regular
structandimpl; you'd get something like:As you can see, when the closure runs it does not need to consume
self; it would be another matter if the method wasHere the
callmethod has to consumeselfbecause we move the value bound toself.x.