Ownership, closures, FnOnce: much confusion

Viewed 1069

I have the following snippet of code:

fn f<T: FnOnce() -> u32>(c: T) {
    println!("Hello {}", c());
}

fn main() {
    let mut x = 32;
    let g  = move || {
        x = 33;
        x
    };

    g(); // Error: cannot borrow as mutable. Doubt 1
    f(g); // Instead, this would work. Doubt 2
    println!("{}", x); // 32
}

Doubt 1

I can not run my closure even once.

Doubt 2

... but I can invoke that closure as many times as I want, provided that I call it through f. Funnily, if I declare it FnMut, I get the same error as in doubt 1.

Doubt 3

What does self refer to in Fn, FnMut and FnOnce traits definition? Is that the closure itself? Or the environment? E.g. from the documentation:

pub trait FnMut<Args>: FnOnce<Args> {
    extern "rust-call" fn call_mut(&mut self, args: Args) -> Self::Output;
}
2 Answers

Some basics about the Fn* trait family is needed to understand how closures actually work. You have the following traits:

  • FnOnce, which, as the name implies, can only be run once. If we look at the docs page we'll see that the trait definition is almost the same as what you specified in your question. What is most important though, is the following: The "call" function takes self, meaning that it consumes the object which implements FnOnce, so like any trait function which takes a self as a parameter, it takes ownership of the object.
  • FnMut, which allows for mutation of the captured variables, or in other words, it takes &mut self. What this means, is that when you make a move || {} closure, it will move any variables you reference which are outside the scope of the closure into the closure's object. The closure's object has a type which is unnameable, meaning that it is unique to each closure. This does force the user to take some kind of mutable version of the closure, so &mut impl FnMut() -> () or mut x: impl FnMut() -> ()
  • Fn, which is generally considered the most flexible. This allows the user to take an immutable version of the object implementing the trait. The function signature for this trait's "call" function is the simplest to understand of the three, as it only takes a reference to the closure, meaning that you don't need to worry about ownership while passing it around or calling it.

To address your individual doubts:

  • Doubt 1: As seen above, when you move something into a closure, the variable is now owned by the closure. Essentially, what the compiler generates is like the following pseudocode:
struct g_Impl {
    x: usize
}
impl FnOnce() -> usize for g_Impl {
    fn call_once(mut self) -> usize {

    }
}
impl FnMut() -> usize for g_Impl {
    fn call_mut(&mut self) -> usize {
        //Here starts your actual code:
        self.x = 33;
        self.x
    }
}
//No impl Fn() -> usize.

And by default it calls the FnMut() -> usize implementation.

  • Doubt 2: What is happening here is that closures are Copy as long as each of their captured variables are Copy, meaning that the closure that is generated will be copied into f, so that f ends up taking a Copy of it. When you change the definition for f to take an FnMut instead, you get the error because you are facing a similar situation to doubt 1: You are trying to call a function which receives &mut self while you've declared the parameter to be c: T instead of either mut c: T or c: &mut T, either of which qualify for &mut self in the eyes of FnMut.
  • Finally, doubt 3, the self parameter is the closure itself, which has captured or moved some variables into itself, so it now owns them.

You are dealing with two different kinds of closures here – FnOnce and FnMut. Both types of closures have different calling conventions.

If you define your closure as

let mut x = 32;
let g  = move || {
    x = 33;
    x
};

the compiler will infer the type of the closure as FnMut. While the closure returns the owned variable x, it can still be called multiple times, since x is Copy, so the compiler choses FnMut as the most general applicable type.

When calling an FnMut closure, the closure itself is passed by mutable reference. This explains your first question – calling g directly does not work unless you make it mutable, since otherwise you can't take a mutable reference to it. I also implicitly answered your third question here – self in the call methods of the Fn traits refers to the closure itself, which can be thought of as a struct containing all captured variables.

When calling f(g), you pass the FnMut closure g as a FnOnce closure to f(). This is allowed since all FnOnce is a supertrait of FnMut, so every closure implementing FnMut also implements FnOnce. Now that the closure has been converted to FnOnce, it is also called according to the FnOnce calling convention:

pub trait FnOnce<Args> {
    type Output;
    extern "rust-call" fn call_once(self, args: Args) -> Self::Output;
}

The closure is passed in by value in this case, so the call consumes the closure. You can give away ownership of any value you own – it does not need to be mutable for this to work.

The reason why you can call g multiple times when calling it through f() is that g is Copy. It only captures a single integer, so it can be copied as many times as you want. Each call to f() creates a new copy of g, which is consumed when it is called inside of f().

Related