There’s really no way to tell what the “best” way to solve this is from just this small example. To explain the problem at hand: the fn(&mut Button)
type is the type of function pointers (with &mut Button
argument and no return value); function pointers can’t capture any state, closures can though.
To discuss one possible way to make it work then: If you change the type of on_click
to a closure type (something that implements the Fn(&mut Button)
trait, or one of its variants, e.g. FnMut(&mut Button)
) then that closure type can capture state. Either Button
would get the closure type as a generic type argument, or you’d work with a trait object like Box<dyn FnMut(&mut Button)>
.
Let me present some example code of how to make this work with a trait object. In order for the closure to capture a mutable reference to the _state
variable, one would still need to introduce a lifetime parameter for the Button
, so use Box<dyn FnMut(&mut Button) + 'a>
instead of just Box<dyn FnMut(&mut Button)>
(which would’ve been equivalent to Box<dyn FnMut(&mut Button) + 'static>
).
Finally, when calling the on_click
callback, you’re giving it a mutable reference to the Button
, but that Button
contains the on_click
callback. This would be a conflicting borrow, since a mutable reference to the Button
means exclusive access to every field of the Button
, including the on_click
being called. One possible approach to solve such issues is by using an Option
and taking out the callback from the Button
, then calling it, and placing it back in afterwards. (Alternative approaches include passing only a reference to some other fields of Button
to the callback; or using immutable references, which might create the need for interior mutability though.)
struct Button<'a> {
on_click: Option<Box<dyn FnMut(&mut Button) + 'a>>,
}
impl Button<'_> {
fn do_something(&self) {}
fn tick(&mut self) {
//if is_clicked() {
let mut on_click = self.on_click.take().unwrap();
on_click(self);
self.on_click = Some(on_click);
//
}
}
struct State {}
impl State {
fn mutate(&mut self) {
println!("...");
}
}
fn main() {
let mut state = State {};
let mut button = Button {
on_click: Some(Box::new(|button: &mut Button| {
button.do_something();
state.mutate();
})),
};
button.tick();
button.tick();
}
Instead of introducing a lifetime to Button
, you could also move
the _state
into the closure (if you don’t need to access it anymore “afterwards”). Or in case of some shared state, (which would need interior mutability btw., so e.g. something like RefCell
) you could wrap it in an Arc
to avoid lifetimes. Here’s the same code, using a move
closure and removing the lifetime argument:
struct Button {
on_click: Option<Box<dyn FnMut(&mut Button)>>,
}
impl Button {
fn do_something(&self) {}
fn tick(&mut self) {
//if is_clicked() {
let mut on_click = self.on_click.take().unwrap();
on_click(self);
self.on_click = Some(on_click);
// }
}
}
struct State {}
impl State {
fn mutate(&mut self) {
println!("...");
}
}
fn main() {
let mut state = State {};
let mut button = Button {
on_click: Some(Box::new(move |button: &mut Button| {
button.do_something();
state.mutate();
})),
};
button.tick();
button.tick();
}