Search code examples
multithreadingrustmutexlifetimeegui

Reference got from mutex'a value doesn't live enought


I'm learning Rust and trying to create a Admin panel like with egui and multi-threading; One thread which update the Student struct in an Hashmap && another which run egui and display Student information; here the problem, I have:

students: Arc<Hashmap<PathBuf, Mutex<Student>>>
actual_student : Option<Mutex<Student>>
// The student in the popup
// Here when I would like to get the &Student after clicking on a button 'See more'
self.actual_student = Some(self.students.get(path.deref()).unwrap());
// But got the error: lifetime may not live enough assignment requires that `'1` must outlive `'a`

Here the complete code of the implementation:

pub struct Maestro<'a> {
    pub students: Arc<HashMap<PathBuf, Mutex<Student>>>,
    pub logger: Logger,
    pub actual_student: Option<&'a Mutex<Student>>,
    // Would like to make like this: pub student: Option<&Student> but cannot get reference to
    // Student from mutex's value
}

impl<'a> Maestro<'a> {
    pub fn new() -> Self {
        let config = load_config();
        let watcher = config.watcher;
        let students = Arc::new(watcher.init());
        let mut students_for_thread = Arc::clone(&students);
        std::thread::spawn(move || {
            watcher.run(&mut students_for_thread);
        });
        Maestro {
            students,
            logger: config.logger,
            actual_student: None,
        }
    }
}

impl<'a> eframe::App for Maestro<'a> {
    fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
        egui::CentralPanel::default().show(ctx, |ui| {
            for (path, student) in self.students.iter() {
                if let Ok(mutex) = student.try_lock() {
                    ui.horizontal(|ui| {
                        ui.label(&mutex.name);
                        match &mutex.bashrc_editable {
                            true => ui.colored_label(egui::Color32::GREEN, "true"),
                            false => ui.colored_label(egui::Color32::RED, "false"),
                        };
                        if ui.button("See more").clicked() {
                            if self.students.contains_key(path) {
                                self.actual_student = Some(self.students.get(path.deref()).unwrap());
                                // FIXME 10/27/22 ectaclick: lifetime may not live long enough assignment requires that `'1` must outlive `'a`
                            }
                        }
                    });
                }
            }
        });
        // Would like to create a 'popup' on the right of the list
        if self.actual_student.is_some() {
            let student = self.actual_student.unwrap().lock().unwrap();
            egui::SidePanel::right("student").show(ctx, |ui| {
                ui.label(student.name.as_str());
                match student.bashrc_editable {
                    true => ui.colored_label(Color32::GREEN, "true"),
                    false => ui.colored_label(Color32::RED, "false"),
                }
            });
        }
        std::thread::sleep(Duration::from_millis(100));
    }

I tried to many type of actual_student:

Option<Student>
Box<Student>
Option<&Student>

Tried to clone the value but the actual_student is not the same of the Student in the Hashmap which is the Student struct updated by the second thread.

But the problem is still the same.


Solution

  • You're getting the lifetime error because if the value is removed the hashmap, the the reference you have in actual_student becomes invalid.

    A direct solution to this is to use Arc<Mutex<Student>> instead of Mutex<Student>

    And the use simply use clone of the Arc object, this is relatively lightweight as it is not copying the whole object.

    But in general, you're trying to think in OOP. a better approach is to use an id, for example:

    actual_student_id : Option<u64> 
    

    which means you'll have to use

    students.iter().find(|it|it.id == actual_student_id)
    

    everytime you want to get a reference to actual student, but that avoids using Arc, and will probably get rid of Mutex too which is even better as mutexes are generally a source of performance hit, sometimes hard to debug.