Completion callback closure and self references

I am trying to implement a completion-closure pattern that I'm used to from Swift, but I'm not sure how to make it compile/work—or the best way to do it in Rust! Playground link

The Manager wants to run tasks. It owns a TaskRunner (which may be replaced with a new instance later). It starts a task, passing a closure to run at completion time. (That task would be async in reality, but for the sake of this example it calls finish_task directly.)

At minimum I want to be able to call a &self method so I can "get the next task" with interior mutability. Ideally I'd like to be able to capture references to other fields within the Manager, on the understanding that they will live at least as long as the TaskRunner and its callback.

Unfortunately I get a lifetime error when I try to do_next_thing(). I have tried a whole bunch of things but rather than dive into the details right away I wanted to ask the general question—am I going about this the right way? What is the most rusty way to solve this inter-struct communication problem? If this is okay, what arrangement of lifetimes makes sense conceptually?

#![feature(fnbox)]
use std::boxed::FnBox;

struct TaskRunner<'a> {
    cb: Option<Box<FnBox() + 'a>>,
}

impl<'a> TaskRunner<'a> {
    fn new() -> Self { TaskRunner { cb: None } }
    
    fn start_task(&mut self, cb: Box<FnBox() + 'a>) {
        self.cb = Some(cb);
    }
    
    fn finish_task(&mut self) {
        if let Some(cb) = self.cb.take() {
            cb.call_box(());
        }
    }
}

struct Manager<'a> {
    task_runner: TaskRunner<'a>,
}

impl<'a> Manager<'a> {
    fn new() -> Self {
        Manager {
            task_runner: TaskRunner::new(),
        }
    }

    fn do_next_thing(&self) {
        println!("Task finished! Going to do the next thing!");
    }
    
    fn run_task(&mut self) {
        self.task_runner.start_task(Box::new(|| {
            println!("Finished!");
            // The following does not compile - how are we even going to use the callback?
            self.do_next_thing();
        } ));
        self.task_runner.finish_task();
    }
}

fn main() {
    let mut manager = Manager::new();
    manager.run_task();
}

Edit: The lifetime is placed on TaskRunner so that captured variables don't need to be static, as described in this blog post. Ideally I'd like to contain this detail to the TaskRunner, i.e. not need a lifetime parameter on Manager, and whatever owns Manager, etc. It's not the main issue here but maybe it's related?

As a sidenote: that feature will be removed most likely following the discussion in the Issue #28796. You should not build on that anymore.

Great news, thanks! :smiley:

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.