Passing a closure that modifies its environment to

2019-07-13 20:12发布

I have a closure that captures and modifies its environment. I want to pass this closure to a function that accepts closures:

fn main() {
    let mut integer = 5;
    let mut closure_variable = || -> i32 {
        integer += 1;
        integer
    };
    execute_closure(&mut closure_variable);
}

fn execute_closure(closure_argument: &mut Fn() -> i32) {
    let result = closure_argument();
    println!("Result of closure: {}", result);
}

Because the closure modifies its environment, this fails:

error[E0525]: expected a closure that implements the `Fn` trait, but this closure only implements `FnMut`
 --> src/main.rs:3:32
  |
3 |       let mut closure_variable = || -> i32 {
  |  ________________________________^
4 | |         integer += 1;
5 | |         integer
6 | |     };
  | |_____^
7 |       execute_closure(&mut closure_variable);
  |                       --------------------- the requirement to implement `Fn` derives from here
  |
note: closure is `FnMut` because it mutates the variable `integer` here
 --> src/main.rs:4:9
  |
4 |         integer += 1;
  |         ^^^^^^^

As I understand from When does a closure implement Fn, FnMut and FnOnce?, this means that my closure actually is expanded to a struct that implements the trait FnMut. This trait is mutable, meaning calling the function changes the (implicit) object. I think this correct, because the variable integer should be modified after calling execute_closure().

How do I convince the compiler this is okay and that I actually want to call a FnMut function? Or is there something fundamentally wrong with how I use Rust in this example?

1条回答
可以哭但决不认输i
2楼-- · 2019-07-13 20:56

If you can change the function that accepts the closure...

Accept a FnMut instead of a Fn:

fn main() {
    let mut integer = 5;
    execute_closure(|| {
        integer += 1;
        integer
    });
}

fn execute_closure<F>(mut closure_argument: F)
where
    F: FnMut() -> i32,
{
    let result = closure_argument();
    println!("Result of closure: {}", result);
}

If you can not change the function that accepts the closure...

Use interior mutability provided by types like Cell or RefCell:

use std::cell::Cell;

fn main() {
    let integer = Cell::new(5);
    execute_closure(|| {
        integer.set(integer.get() + 1);
        integer.get()
    });
}

fn execute_closure<F>(closure_argument: F)
where
    F: Fn() -> i32,
{
    let result = closure_argument();
    println!("Result of closure: {}", result);
}

Or is there something fundamentally wrong with how I use Rust in this example?

Perhaps. An argument of type &mut Fn() -> i32 cannot mutate the variables it has closed over, so the error message makes sense to me.

It's kind of similar to the type &mut &u8 — you could alter the outer reference to point to another immutable reference, but you cannot "ignore" the inner immutability and change the numeric value.

Aside:

The original code uses dynamic dispatch because there is a trait object that provides indirection. In many cases you'd see this version that I posted above, which uses static dispatch and can be monomorphized. I've also inlined the closure as that's the normal syntax.

Here's the original version with just enough changes to work:

fn main() {
    let mut integer = 5;
    let mut closure_variable = || -> i32 {
        integer += 1;
        integer
    };
    execute_closure(&mut closure_variable);
}

fn execute_closure(closure_argument: &mut FnMut() -> i32) {
    let result = closure_argument();
    println!("Result of closure: {}", result);
}
查看更多
登录 后发表回答