Callback as struct field and to be used in thread

I am learning Rust and would like to achieve same following function as implemented in C++ and unable to figure out how to reslove the lifetime. Maybe my entire program structure is wrong.

Device has a serial port and command queue which has command string and option callback. Once it is created, a thread is monitoring whether the port name is changed. If it is changed, then open a port with new port name, and spawn two thread: one thread is continuously reading the port; another thread is continuously get the command from the command queue and send the command string to the port. If the command has callback, then call the callback after sending the string to the port.

The issue is with the lifetime of callback. Hope someone guide me to fix this or a new approach to achieve such application.

error: lifetime may not live long enough
--> src/main.rs:19:59
|
17 | impl<'a> Device<'a>{
| -- lifetime 'a defined here
18 | fn send_cmd_with_callback1(&mut self, cmd_str:String){
| - let's call the lifetime of this reference '1
19 | let cmd = Command{command:cmd_str, callback: Some(Box::new(||{self.callback1()}))};
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ coercion requires that '1 must outlive 'a

error[E0501]: cannot borrow self.commands as immutable because previous closure requires unique access
--> src/main.rs:20:9
|
17 | impl<'a> Device<'a>{
| -- lifetime 'a defined here
18 | fn send_cmd_with_callback1(&mut self, cmd_str:String){
19 | let cmd = Command{command:cmd_str, callback: Some(Box::new(||{self.callback1()}))};
| ------------------------------
| | | |
| | | first borrow occurs due to use of *self in closure
| | closure construction occurs here
| coercion requires that *self is borrowed for 'a
20 | self.commands.lock().unwrap().push_back(cmd);
| ^^^^^^^^^^^^^ second borrow occurs here
|
= note: due to object lifetime defaults, Box<dyn FnMut() + Send> actually means Box<(dyn FnMut() + Send + 'static)>

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=cb91edd80ada1cdefc22ddb4ef87357a

use std::sync::{Arc, Mutex};
use std::thread;
use std::collections::VecDeque;

pub struct Command<'a> {
    pub command: String,
    pub callback: Option<Box<dyn FnMut() + Send + 'a>>,
}

#[derive(Clone, Default)]
pub struct Device<'a>{
    port_name: Arc<Mutex<String>>,
    port_changed: Arc<Mutex<bool>>,
    commands: Arc<Mutex<VecDeque<Command<'a>>>>
}

impl<'a> Device<'a>{
    fn send_cmd_with_callback1(&mut self, cmd_str:String){
        let cmd = Command{command:cmd_str, callback: Some(Box::new(||{self.callback1()}))};
        self.commands.lock().unwrap().push_back(cmd);
    }
    
    fn run_forever(&mut self){
        let port_changed = self.port_changed.clone();
        let port_name = self.port_name.clone();
        let commands = self.commands.clone();
        thread::scope(|s|{
            loop{
                // if the port is changed, then open the new port and create read thread and write thread
                if  *port_changed.lock().unwrap(){ 
                    let port:Option<String> = None; 
                    // just to satisfy playground compiler
                    // the actual code is to open a port with serial2::SerialPort::open() 
                    // if open error then continue the outer loop
                    // the port is opened successfully then     
                    // spawn a thread to read from the serial port.
                    let port = Arc::new(port);
                    s.spawn(||{
                        loop{
                            if  *port_changed.lock().unwrap(){
                                    break; // thread will terminate
                            }
                            // continous read from port
                            // ....
                        }
                    });
                        
                    // Spawn a thread to write to the serial port
                    s.spawn(||{
                        loop{
                            if  *port_changed.lock().unwrap(){
                                    break; // thread will terminate
                            }
                            // continuous check command vec and write to serial port
                            if let Some(command) = commands.lock().unwrap().pop_front() {
                                    // get command string 
                                    let cmd_str = command.command;
                                    // then send the cmd_str to the serial port 
                                    // ...
                                        
                                    // if the command has callback
                                    if let Some(mut callback) = command.callback{
                                        callback();
                                    } 
                            }
                        }
                    });
                }
            }
        });
    }
    
    fn callback1(&mut self){
        
    }
    
    fn callback2(&mut self){
        
    }
}

fn main() {
    let dev = Device{port_name:Arc::new(Mutex::new("COM1".to_string())),
    port_changed: Arc::new(Mutex::new(true)), 
    ..Default::default()
    };
}
    fn send_cmd_with_callback1(&mut self, cmd_str: String) {
        let cmd = Command {
            command: cmd_str,
            callback: Some(Box::new(|| self.callback1()))
        };
        self.commands.lock().unwrap().push_back(cmd);
    }

The callback closure captures &mut self and then tries to store it in self.commands, so this is a self-referential struct.

Beyond that I think you're trying to call the callback concurrently with an exclusive reference at line 64, but I don't have the time to dive deeper at this moment, so I'll just leave it at that and hopefully someone else can follow up.

It looks like the issue depends on the fact that you are calling self.callback1 inside the closure. This borrows self, which is also already exclusively mutably borrowed by send_cmd_with_callback1 method.
If you make callback1 an external function, the compilation error disappears, but IDK if this is suitable for your use case. If yur callback need data from inside the Device instance, you could try to clone them and pass the clone as arguments...

I am still learning Rust and I have never used it for anything serious. But if I was going to implement something like what you describe ( which I admit I did not fully understand ) I would attempt to make use of use std::sync::mpsc. The idea would be to have the thread(s) that listen on the serial port(s) send events on the queue, and the main thread ( or a dedicated server thread ) to receive the events and call the callbacks accordingly. This would avoid issues with different callbacks called by different threads and operating on the same data.