Hi,
I'm trying to implement the following callback system:
- A frame source (CAN bus) is created
- One or more devices are created and their frame handlers + userdata are registered as callbacks in the frame source instance
- Whenever a frame arrives, all/specific handlers are called with their userdata as argument
Currently my implementation is like this:
use std::any::Any;
use std::sync::{Arc, Mutex};
use std::{thread, time::Duration};
type Callback = fn(&[u8], Arc<Mutex<dyn Any>>) -> Result<(), Box<dyn std::error::Error + '_>>;
#[derive(Default)]
pub struct FrameSource {
parser: Vec<(Callback, Arc<Mutex<dyn Any>>)>,
}
#[derive(Default)]
struct Device {
data1: u8,
data2: u8,
}
impl FrameSource {
pub fn register_parser(&mut self, parser: Callback, callback: Arc<Mutex<dyn Any>>) {
println!("registering callback");
self.parser.push((parser, callback));
}
pub fn process_callbacks(&mut self) -> Result<(), Box<dyn std::error::Error + '_>> {
let tmp: [u8; 10] = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
println!("processing callbacks");
for (parser, callback) in self.parser.iter() {
match parser(&tmp, callback.clone()) {
Ok(_) => (),
_ => {
println!("failed to process callback");
}
}
};
Ok(())
}
}
fn frame_processor(frame: &[u8], instance: Arc<Mutex<dyn Any>>) -> Result<(), Box<dyn std::error::Error + '_>> {
let instance = instance.clone();
let mut instance = instance.lock().unwrap();
let instance = match instance.downcast_mut::<Device>() {
Some(instance) => instance,
None => return Err("failed to cast into Device instance".into())
};
instance.data1 += frame[0];
instance.data2 += frame[1];
println!("frame_processor called, data1: {}, data2: {}", instance.data1, instance.data2);
Ok(())
}
fn parser(frame: &[u8], data: Arc<Mutex<dyn Any>>) -> Result<(), Box<dyn std::error::Error + '_>> {
frame_processor(frame, data)
}
fn main() {
let mut frame_source = FrameSource::default();
let device = Arc::new(Mutex::new(Device::default()));
frame_source.register_parser(parser, device);
loop {
frame_source.process_callbacks().unwrap();
thread::sleep(Duration::from_secs(1));
}
}
This is not very efficient as every frame_processor needs to unpack the userdata.
When I try to unpack it in the parser Rust will complain and I'm not sure how to fix this:
fn frame_processor(frame: &[u8], instance: &Device) -> Result<(), Box<dyn std::error::Error + '_>> {
instance.data1 += frame[0];
instance.data2 += frame[1];
println!("frame_processor called, data1: {}, data2: {}", instance.data1, instance.data2);
Ok(())
}
fn parser(frame: &[u8], data: Arc<Mutex<dyn Any>>) -> Result<(), Box<dyn std::error::Error + '_>> {
let instance = data.clone();
let mut instance = instance.lock().unwrap();
let instance = match instance.downcast_mut::<Device>() {
Some(instance) => instance,
None => return Err("failed to cast into Device instance".into())
};
frame_processor(frame, instance)
}
Error message:
error[E0106]: missing lifetime specifier
--> src/main.rs:42:95
|
42 | fn frame_processor(frame: &[u8], instance: &mut Device) -> Result<(), Box<dyn std::error::Error + '_>> {
| ----- ------- ^^ expected named lifetime parameter
|
= help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `frame` or `instance`
help: consider introducing a named lifetime parameter
|
42 | fn frame_processor<'a>(frame: &'a [u8], instance: &'a Device) -> Result<(), Box<dyn std::error::Error + 'a>> {
| ++++ ++ ++ ~~
error[E0228]: the lifetime bound for this object type cannot be deduced from context; please supply an explicit bound
--> src/main.rs:42:71
|
42 | fn frame_processor(frame: &[u8], instance: &Device) -> Result<(), Box<dyn std::error::Error + '_>> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
After adding the suggestion it still won't compile:
fn frame_processor<'a>(frame: &'a [u8], instance: &'a mut Device) -> Result<(), Box<dyn std::error::Error + 'a>> {
Error:
error[E0515]: cannot return value referencing local variable `instance`
--> src/main.rs:59:5
|
54 | let instance = match instance.downcast_mut::<Device>() {
| --------------------------------- `instance` is borrowed here
...
59 | frame_processor(frame, instance)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ returns a value referencing data owned by the current function
error[E0515]: cannot return value referencing local variable `instance`
--> src/main.rs:59:5
|
53 | let mut instance = instance.lock().unwrap();
| --------------- `instance` is borrowed here
...
59 | frame_processor(frame, instance)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ returns a value referencing data owned by the current function
Is there a way to unpack the userdata only once and then allow to write slim parsers?
Or maybe is there a completely other Rust-way to do it? The struct Device
will change per registered processor, so it can be DeviceLed
, DeviceButton
etc.
Thanks in advance!
Sven