Questions about implementing FnOnce and FnMut

Hi there, I have two questions about FnMut and FnOnce (which I realize are currently marked as unstable)

I'm playing around with the FnMut and FnOnce traits, trying to implement them on a struct. Here's an example of what I first tried:

struct Handler {
    call_count: u32
}


impl FnMut<()> for Handler {
    extern "rust-call" fn call_mut(&mut self, args: ()) -> u32 {
        println!("call_mut()");
        self.call_count += 1;
        self.call_count
    }
}

Trying to compile this I get the following error: the trait 'core::ops::FnOnce<()>' is not implemented for the type 'Handler' [E0277]. Reading the docs, it's clear that FnMut requires an impl for FnOnce, so my first question is: why is this the case?

In my example where I increment call_count (thus requiring a mutable self), I'm not actually able to provide a meaningful body to a fn call_once. So just to get something to compile, the body of fn call_once is just a panic!().

To test this, it's simple:

fn main() {
    let mut h = Handler {call_count: 0};
    h();
    h();
    h();
    println!("call_count is {}", h.call_count);
    
}

This works as expected -- "call_mut()" is printed 3 times, and the call_count is set to 3 at the end.

To continue testing, I tried to create a non-mutable Handler:

fn main() {
    let h = Handler {call_count: 0};
    h();
}

I had thought that since h was immutable, the FnOnce trait would be used. But this isn't the case, as rust give the following error:

a.rs:27:5: 27:6 error: cannot borrow immutable local variable `h` as mutable
a.rs:27     h();

So it looks like to implement FnMut, I have to also implement FnOnce, but there isn't an obvious way to call the FnOnce impl? This also confuses me.

I feel like I'm missing some key understanding about how these traits are designed. Please help :smile:

(Using rust nightly from f207ecbe0 playpen link: Rust Playground)

Thanks,
-Andrew

The traits are designed this way because the represent nested sets of types:

  • any closure can definitely be called once (it would be pointless to call something that can't be called a function), FnOnce,
  • some closures can be called once, but also more than once if they're allowed to mutate their captured variables FnMut,
  • even fewer closures don't even need to mutate their captured variables (they can still be called via a pointer that gives them that choice, they just don't need to use it), Fn.

That is, the behaviour of every Fn can also be used in a FnMut, and similarly the behaviour of a FnMut can also be used in a FnOnce. The trait heirarchy represents this: Fn: FnMut, FnMut: FnOnce, and it is expected that if any is implemented, they're all implemented the same (this is part of the reason manual implementations are experimental).

The usual way to do this is to just implement once and call that from the other traits.

impl FnOnce<()> for Handler {
    extern "rust-call" fn call_once(mut self, args: ()) -> u32 {
        self.call_mut(args)
    }
}

The FnOnce impl is used by generic code, e.g. you can pass a FnMut to a generic function that expects a FnOnce (like, say, Option::map). It is very bad on the part of implementers to have the behaviour different.

(The closure trait selection doesn't look at the mutability, it just chooses the most flexible trait that is implemented, i.e. in order of priority, Fn, FnMut, FnOnce.)

5 Likes

Great answer, thank you!