Closures have some data in their state, but how do I make it mutable? For example, I want a counter closure which returns the incremented value each time, but it doesn't work. How do I make it work?
fn counter() -> Box<Fn() -> i32> {
let mut c: i32 = 0;
Box::new(move || {
c += 1;
c
})
}
fn main() {
let mut a = counter();
let mut b = counter();
println!("{:?}", [a(), a(), a(), b(), b(), a()]);
}
Error (and warning) I'm getting:
error: cannot assign to captured outer variable in an `Fn` closure
c += 1;
^~~~~~
help: consider changing this closure to take self by mutable reference
Box::new(move || {
c += 1;
c
})
I expect it to output something like [1, 2, 3, 1, 2, 4]
.
As the error message says:
cannot assign to captured outer variable in an
Fn
closure
Instead, you want a FnMut
closure:
fn counter() -> Box<FnMut() -> i32> {
let mut c = 0;
Box::new(move || {
c += 1;
c
})
}
fn main() {
let mut a = counter();
let mut b = counter();
let result = [a(), a(), a(), b(), b(), a()];
println!("{:?}", result);
assert_eq!([1, 2, 3, 1, 2, 4], result);
}
As the FnMut
docs say:
A version of the call operator that takes a mutable receiver.
This allows the closure to mutate the contained state.
Incidentally, the explicit type for c
is not needed.
What confuses me, is that
pub trait Fn<Args>: FnMut<Args>
. Doesn't it mean thatFn
(what I used) should support behaviour ofFnMut
?
Perhaps When does a closure implement Fn, FnMut and FnOnce? can help provide some background information. This is an aspect I get intuitively, but haven't figured out how best to communicate. This section from Finding Closure in Rust also seems relevant:
At a high-level
self
gives implementers (i.e. the types users define to implement the trait) the most flexibility, with&mut self
next and&self
the least flexible. Conversely,&self
gives consumers of the trait (i.e. functions with generics bounded by the trait) the most flexibility, andself
the least.
In short, that trait definition says that any FnMut
closure can be used as a Fn
closure. This makes some sense, as we can simply ignore the mutability. You cannot go the other way - you cannot make an immutable reference into a mutable one.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With