Search code examples
rustclosures

in Rust, why can FnOnce mutate values?


I've always seen FnOnce as a function that can consume value in its scope, but not mutate, that would be FnMut and you would need a mut closure to do things like that.

Yet, this example seems to work:

#[derive(Copy, Debug, Clone)]
struct Thing(u8);

fn thing<T>(closure: T) -> Result<Thing, ()> where T: FnOnce() -> Result<Thing, ()> {
    closure()
}

fn main() {
    let mut a = None;
    let b = || {
        a = Some(Thing(8));
        a.ok_or_else(|| panic!("yo"))
    };
    let c = thing(b).unwrap();
    println!("{a:?}");
    println!("{c:?}");
}

and prints:

Some(Thing(8))
Thing(8)

Effectively, the non-mut closure can mutate mutable variable in its environment if it's cast to an FnOnce and used only once. Why isn't Rust enforcing an FnMut and a mutable closure here? I'm confused.


Solution

  • Why isn't Rust enforcing an FnMut and a mutable closure here?

    There are two parts to this question:

    1. why isn't an FnMut bound required, though we're clearly mutating a captured value?

    2. why doesn't Rust require capture: T to be mut capture: T, and b to be mut b?

    The answer to both question is the same, and boils down to: because we're not mutating those variables, we're passing ownership. (The first revision of this answer was actually confused about the answer to the first question, stating that every FnOnce value is automatically FnMut, which is untrue - it's actually the other way around, it's FnMut that implies FnOnce.)

    Starting from b, thing(b) doesn't mutate b, it passes ownership of b to thing(), and that doesn't require mut regardless of whether thing() will end up mutating the value (which the caller can't even know). Likewise, invoking () on an FnOnce closure doesn't mutate capture, it passes ownership of capture to the () operator of FnOnce, which takes self by value.

    Consider a simpler example that does the same thing:

    // we mutate `v` even though it's not mut
    fn mutate_owned(v: Vec<u32>) {
        let mut foo = v;
        foo.push(42);
        println!("{foo:?}")
    }
    
    fn main() {
        // we'll end up mutating `b` even though it's not mut
        let b = vec![1, 2, 3];
        mutate_owned(b);
    }