Best way to define a callback closure that captures it's environment

Hi, I'm writing a GUI system, and I can't figure out how to capture and mutate state in a callback passed to clickables. Code that doesn't capture state:

struct Button {
    on_click: fn(&mut Button),
}

impl Button {
    fn do_something(&self) {}
    fn tick(&mut self) {
        //if is_clicked() {
        (self.on_click)(self);
        //}
    }
}

struct State {}

fn main() {
    let mut _state = State {};

    let _button = Button {
        on_click: |button: &mut Button| {
            button.do_something();
            //_state.mutate();
        },
    };
}

The application state can be of any type (determined by the user of the GUI system), so I can't specify the type in the definition of on_click.

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();
}
1 Like

Hi, I find it really difficult to wrap my head around lifetimes. I just ended up using sync::mpsc, Using the channel's generic type as a generic type argument to Button to store a Sender inside the Button struct. Like so:

use std::sync::mpsc::{self, Receiver, Sender};

struct Button<T> {
    on_click: fn(&mut Button<T>),
    tx: Sender<T>,
}

impl<T> Button<T> {
    fn do_something(&self) {}
    fn tick(&mut self) {
        //if is_clicked() {
        (self.on_click)(self);
        //}
    }
}

struct State {}

enum Message {
    One,
    Two,
}

fn main() {
    let mut _state = State {};
    let (tx, rx): (Sender<Message>, Receiver<Message>) = mpsc::channel();
    let mut button = Button {
        on_click: |button: &mut Button<Message>| {
            button.do_something();
            button.tx.send(Message::One).unwrap();
        },
        tx,
    };
    loop {
        button.tick();
        if let Ok(message) = rx.try_recv() {
            //match and state.mutate();
        }
    }
}

This is not a solution to the question, but for my particular use case, this appears simplest. I apologise if the question wasn't verbose enough.

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.