Book: Ch13.1: `FnOnce` can absolutely be called more than once

Created on 26 Jun 2017  Â·  8Comments  Â·  Source: rust-lang/book

In Chapter 13.1, we have the text:

FnOnce takes ownership of the variables it captures from the environment and moves those variables into the closure when the closure is defined. Therefore, a FnOnce closure 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.

Q for nostarch

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 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.

All 8 comments

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:

FnOnce takes ownership of the variables it captures from the environment and moves those variables into the closure when the closure is defined.

to:

FnOnce takes 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.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

BedfordWest picture BedfordWest  Â·  4Comments

serialhex picture serialhex  Â·  4Comments

ctsa picture ctsa  Â·  3Comments

carols10cents picture carols10cents  Â·  5Comments

drandreaskrueger picture drandreaskrueger  Â·  4Comments