Strange behaviour with closures

Hello, I need some help on a code with closures.
I wrote this code that creates a closure with a counter inside.
But the behaviour is strange if I call this closure in a function with a function object as parameter.

--- correct behaviour ---
1 2 3 4 5 6 7 8 
--- strange behaviour ---
1 1 1 2 3 3 3 4 

I have no issue with this:

fn run_mut(f: &mut impl FnMut()) { f(); }

But I have issues with this :

fn run_mut(mut f: impl FnMut()) { f(); }

Code

fn run_mut(mut f: impl FnMut()) { f(); } 

fn main() {
    println!("--- correct behaviour ---");
    let mut f = {
        let mut counter = 0;
        move || { 
            counter += 1; 
            print!("{} ", counter); 
        }
    };

    run_mut(&mut f); // 1
    run_mut(&mut f); // 2
    f(); // 3
    f(); // 4
    run_mut(&mut f); // 5
    run_mut(&mut f); // 6
    f(); // 7
    f(); // 8

    println!();
    println!("--- strange behaviour ---");
    let mut f = {
        let mut counter = 0;
        move || { 
            counter += 1; 
            print!("{} ", counter); 
        }
    };

    run_mut(f); // 1 counter = 0 + 1 ok
    run_mut(f); // 1 counter = 0 + 1 ok
    f(); // 1  counter = 0 + 1 ok
    f(); // 2  counter += 1 ok
    run_mut(f); // 3  counter += 1 ???
    run_mut(f); // 3  counter = counter ???
    f(); // 3 counter = counter ???
    f(); // 4 counter += 1 ok but strange
    // how Rust can print "counter" without increment it ?
}

Output

--- correct behaviour ---
1 2 3 4 5 6 7 8 
--- strange behaviour ---
1 1 1 2 3 3 3 4 

I don't understand how Rust can print the counter without increment it,
and why Rust lets you pass a function as an object instead of a mut reference.

Please help me !

1 Like

The reason is that your f is Copy - this can be proved by this code (which compiles):

fn assert_copy<T: Copy>(_: T) {}

fn main() {
    let mut f = {
        let mut counter = 0;
        move || { 
            counter += 1; 
            print!("{} ", counter); 
        }
    };
    assert_copy(f);
}

So, when you pass f to run_mut by-value, you pass not the f itself, but its copy. The counter is incremented in the copy, but unchanged in the original closure. The first fact can be shown by replacing run_mut with run_twice:

fn run_twice(mut f: impl FnMut()) {
    print!("First run: ");
    f();
    println!();
    print!("Second run: ");
    f();
    println!();
}

fn main() {
    println!("--- passing by reference ---");
    let mut f = {
        let mut counter = 0;
        move || {
            counter += 1;
            print!("{} ", counter);
        }
    };

    run_twice(&mut f); // 1, 2
    f(); // 3
    f(); // 4
    println!();
    run_twice(&mut f); // 5, 6
    f(); // 7
    f(); // 8

    println!();
    println!("--- passing copy ---");
    let mut f = {
        let mut counter = 0;
        move || {
            counter += 1;
            print!("{} ", counter);
        }
    };

    run_twice(f); // 1, 2
    f(); // 1
    f(); // 2
    println!();
    run_twice(f); // 3, 4
    f(); // 3
    f(); // 4
}

Also note that, if f captured something non-Copy, such as Vec, your original code would not compile:

fn run_mut(mut f: impl FnMut()) {
    f();
}

fn main() {
    let mut f = {
        let mut counter = vec![0];
        move || {
            counter[0] += 1;
            print!("{} ", counter[0]);
        }
    };

    run_mut(f);
    run_mut(f); // error - use of moved value
}
3 Likes

Thanks Cerber-Ursi, you're the boss !

Now it's clear : it's a copy of the closure.
And the "run_twice" function is a good idea to see what's happen.

Thanks a lot.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.