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)>
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()
};
}