About lifetime of closure problem

I have a worker and it accepts some functions to execute,just like

struct App {
    worker: Worker
}

impl Worker {
    pub fn run<F>(&self, f: F) where F: (FnOnce() -> Box<dyn Updater>) + Send + 'static,{
        todo!()
    }
}

the worker is a property of app, the closure received by the worker maybe will capture references in the environment, the references come from the app structure. What should I do

It's not clear what problem you are facing. Perhaps provide a concrete example code illustrating that? Anyway, the most straight-forward way to annotate lifetime is like this:

impl Worker {
    pub fn run<'a, F>(&self, f: F) 
        where F: (FnOnce() -> Box<dyn Updater + 'a>) + Send + 'a
    {
        todo!()
    }
}

if you are calling the woker to process some states of the App, you probably don't want to store Worker as a field of App, otherwise, you just flight the borrow check for no good. when it comes to struct composition, you should really think about what owns what. when I was first learning rust coming from a C++ background, I tried to store every states in a big struct, typically called Context, Manager or the like, just for convinience. but the compiler doesn't allow me, so I put Rc or Cell everywhere. it took me quite some time to be able to think and design data structure "the rust way".

in your paticular case, you might workaround by store both App and Worker as fields of some larger struct.

if you have a self: &mut App, you might workaround it by store the worker field as a Option<Worker>, and you take the worker out before calling worker.run(|| {...}), and place it back after the call. you can do achieve similar effect with Cell<Worker>, but you should be careful about what to put inside the cell when you take the worker out.

if you have to store the worker as a field of app, you can try partial-borrow

2 Likes
struct App {
    openid: String,
    helper: OmsHelper,
    worker: Worker
    ...
}

impl eframe::App for App {
    
    fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
        ...
        let mut button1 = DebounceButton::from_memory("***",1, ctx);
        if button1.show(ui).clicked() {
            self.worker.run(||{
                let rs = self.helper.create_prepare_order();
                Box::new(rs)
            });
            button1.store_memory(ctx);
        }
    }
    
}

struct Worker {
    thread: thread::JoinHandle<()>,
    jobs: Arc<Mutex<Vec<Job>>>,
}

impl Worker {
    pub fn run<F>(&self, f: F) where F: (FnOnce() -> Box<dyn Updater>) + Send + 'static,{
        loop {
            let tl = self.jobs.try_lock();
            match tl {
                Ok(mut v) => {
                    let job = Box::new(f);
                    v.push(job);
                    break;
                },
                Err(e) => {}
            }
        }
    }
}

This is the roughly complete code

It's not a runnable code snippet, so I assume you are trying creating some Job for background worker thread to do when a button is clicked. And the problem arose from the fact that the created Job is borrowing App.

Simply speaking, just avoid borrow App if possible. For example: Does create_prepare_order requires &mut OmsHelper? if not, you can wrap the helper in Arc and clone the Arc to avoid borrowing App. If it does, then you have to wrap some kind of synchronize mechanic.

Btw, it seems you are trying to emulate a thread-safe queue with Arc<Mutex<Vec<Job>>>. You can use std::sync::mpsc::channel instead. (And loop { self.jobs.try_lock() ... } is just a worse version of let jobs = self.jobs.lock())

Thanks for your help.

I think about "wrap some kind of synchronize mechanic" this topic, maybe i just can use Arc<Mutex> to resolve this problem. If you have better solution, tell me plz.

std::sync::mpsc::channel is a good solution :grinning:

Arc<Mutex<T>> is the "easy" way, or the quickest way to get your code running. Since I have no idea how OmsHelper::create_prepare_order work I can't provide much more information.

One thing that do look suspicious is that why are you sending Box<dyn FnOnce() -> Box<dyn Updater>> instead of Box<dyn Updater> itself? If OmsHelper::create_prepare_order is very cheap then you can just call that in the main thread.

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.