Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating a callback system using closures

Tags:

closures

rust

I'm trying to make something like a "callback system". For example, there is a window and a couple of buttons in it. The window sets callbacks for each button. Both callbacks should change the state of the window. The compiler doesn't allow capturing &self in my closures / callbacks, and I don't know how to make it work.

Are there any common patterns for callbacks I should be following?

This is an easy example as all components have the same lifetime. What if the components have different lifetimes?

struct Button<'a> {
    f: Option<Box<Fn() + 'a>>,
}

impl<'a> Button<'a> {
    fn new() -> Button<'a> { Button { f: None } }
    fn set<T: Fn() + 'a>(&mut self, f: T) { self.f = Some(Box::new(f)); }
    fn unset(&mut self) { self.f = None; }
    fn call(&self) { match self.f { Some(ref f) => f(), None => () } }
}

struct Window<'a> {
    btn: Button<'a>,
    //btns: Vec<Button<'a>>,
}

impl<'a> Window<'a> {
    fn new() -> Window<'a> {
        Window { btn: Button::new() }
    }

    fn hi(&mut self) { // self is mutable
        println!("callback");
    }

    fn run(&mut self) {
        // Compile error: cannot infer an appropriate lifetime for
        // capture of `self` by closure due to conflicting requirements
        self.btn.set(|| self.hi()); // How to make it work?
        self.btn.call();
        self.btn.unset();
    }
}

fn main() {
    let mut wnd = Window::new();
    wnd.run();
}
like image 948
Marvel Avatar asked Apr 09 '15 13:04

Marvel


Video Answer


1 Answers

You are going to run into immediate problems with this line:

self.btn.set(|| self.hi());

Here, you need to borrow self as mutable in order to modify btn. You are also trying to borrow self as mutable in the closure. This is going to immediately run into problems because Rust does not allow you to have multiple mutable references to the same object (known as aliasing). This is a fundamental part of the the memory-safety guarantees of the language.

Also, conceptually you are trying to set up a cycle of references - the Window knows about Button and Button knows about Window. While this is possible, it often isn't what you want. Once the references have a cycle, it's very hard to disentangle them. You can also search other questions that ask about creating graphs in Rust (as opposed to trees) to see similar issues other people have had.

Ideally, you can structure your code as a tree. Here, I chose that Button can know about Window, but not vice versa:

struct Button<'a> {
    f: Option<Box<FnMut() + 'a>>,
}

impl<'a> Button<'a> {
    fn new() -> Button<'a> { Button { f: None } }
    fn set<T: FnMut() + 'a>(&mut self, f: T) { self.f = Some(Box::new(f)); }
    fn unset(&mut self) { self.f = None; }
    fn call(&mut self) { match self.f { Some(ref mut f) => f(), None => () } }
}

struct Window;

impl Window {
    fn hi(&mut self) {
        println!("callback");
    }
}

fn main() {
    let mut wnd = Window;
    let mut btn = Button::new();
    btn.set(|| wnd.hi());
    btn.call();
    btn.unset();
}
like image 132
Shepmaster Avatar answered Oct 29 '22 03:10

Shepmaster