I have a History
type which contains an initial state, a current state, and a list of closures with every change required to compute the current state from the initial state. These are applied with an .apply(...)
method which takes a boxed closure, uses it to modify the current state, and adds it to the list. Because I want these to be deterministically reusable they are Fn
, not FnMut
or FnOnce
.
struct History<State: Clone> {
initial: State,
current: State,
updates: Vec<Box<dyn Fn(&mut State)>>,
}
impl<State: Clone> History<State> {
fn apply(&mut self, update: Box<dyn Fn(&mut State)>) {
update(&mut self.current);
self.updates.push(update);
}
}
I am currently taking closures as Box<dyn Fn(&mut State)>
, and it works fine:
fn main() {
let mut history = History::<usize> {
initial: 0,
current: 0,
updates: vec![],
};
let delta = 10;
history.apply(Box::new(move |mut value| *value += delta));
println!("{}", history.current);
}
10
This got me thinking about whether it would be possible for a method to accept arbitrary unboxed closures by using impl Trait
instead of dyn Trait
. In this case, our method could box the closures itself, so the call site would become:
history.apply(move |mut value| *value += delta);
(Please entertain the question of whether this is even possible, even if it's a bad idea in this case.)
I am imagining that each closure site is like a distinct data type, instantiated with the enclosed values each time it's used, so impl
Trait could specialize the method for each implicit closure like it does for each explicit type. But I'm not sure whether Rust actually works like that.
However, when I try making the change in the code, I get a new lifetime error:
fn apply(&mut self, update: impl Fn(&mut State)) {
update(&mut self.current);
self.updates.push(Box::new(update));
}
error[E0310]: the parameter type `impl Fn(&mut State)` may not live long enough
--> src/main.rs:10:27
|
10 | self.updates.push(Box::new(update));
| ^^^^^^^^^^^^^^^^
|
note: ...so that the type `impl Fn(&mut State)` will meet its required lifetime bounds
--> src/main.rs:10:27
|
10 | self.updates.push(Box::new(update));
| ^^^^^^^^^^^^^^^^
This confuses me. I'm not sure where there's a reference that could be going bad.
In my head, the entire closure state is now being moved into apply
via the impl Fn
parameter, then moved into a Box
that is owned by self
. But it's complaining that I can't move the contents to a box, because I have a potentially-stale reference, not just owned data? Where am I borrowing anything? Why didn't this occur when I boxed the closure directly in main
instead of in apply
?
Is it possible to use impl Fn
to accept an (arbitrarily-sized) closure as an argument? If so, how?