Is FnOnce(&mut ... ) equals to FnMut( ... )?


#1

I know the general rules of them are:

  • FnOnce (self) are functions that can be called once,
  • FnMut (&mut self) are functions that can be called if they have &mut access to their environment
  • Fn (&self) are functions that can still be called if they only have & access to their environment.

But today I encountered such piece of code:

pub struct ActivePageTable { ... }
pub struct InActivePageTable { ... }
...
impl InActivePageTable {
    pub fn with<F>(&mut self,
                   table: &mut InactivePageTable,
                   f: F)
        where F: FnOnce(&mut ActivePageTable) {...}
}

Here, can I rewrite FnOnce(&mut ActivePageTable) to FnMut(ActivePageTable), because FnMut will automatically do a mutable borrow &mut ?


#2

FnOnce(&mut ActivePageTable) is a function that can be called once which is passed a mutable reference to an ActivePageTable. FnMut(ActivePageTable) is a function that can be called multiple times which is passed an ActivePageTable by value. The mutability of the function and it’s argument aren’t really related in any way.


#3

When you see FnOnce, FnMut, and so on, they are referring to the mutability/reusability of the closure object itself, not the arguments that are passed through it.

It’s basically all about the fact that closures can capture state from their environment. When you write code like this…

let x = 40;
let f = || x + 2;

…the closure acquires a regular reference to the data “x”, so you get an Fn().

When you write code like this…

let mut x = 30;
let f = || { x += 12; };

…the closure now needs to modify its environment, so it acquires an &mut to x, and you get an FnMut().

Finally, when you do something like this…

let s = String::new("42");
let f = || s;

…the closure moves state out when it’s called. So it needs to capture s by move and also output it by value. Which means that you can only safely call F once, because after that the internal “s” state has been trashed by moving it out. And thus, you get an FnOnce().


#4

@HadrienG
@sfackler
Thank for your explanation. I get it. :slight_smile: