Trait function calls member in thread

Combining 2 past questions:

  1. Rust inheritance trait functions call private non-trait member functions
  2. How to run a member function in a thread from another member function

For my first question, I got this solution: playground-solution-inheritance-trait and it worked in a single-threaded environment.
Now I want to call the member function in a thread.
First I try to call it in a "simple" way: playgreound, and got the same error that was solved in the second question:

   |
28 |       fn run(&self) {
   |              -----
   |              |
   |              `self` is a reference that is only valid in the method body
   |              let's call the lifetime of this reference `'1`
...
32 |           workers_vec.push(thread::spawn(move || {
   |  __________________________^
33 | |             runner.record_image();
34 | |         }));
   | |          ^
   | |          |
   | |__________`self` escapes the method body here
   |            argument requires that `'1` must outlive `'static`

I implemented the fix from the second question: playgreond run member function using <self: &Arc>, and then I got this error:

   |
5  |     fn run(self: &Arc<Self>);
   |                  ---------- help: consider changing method `run`'s `self` parameter to be `&self`: `&Self`
...
67 | fn perform_recording(recorders: &[Box<dyn Record>]) {
   |                                       ^^^^^^^^^^ `Record` cannot be made into an object
   |
note: for a trait to be "object safe" it needs to allow building a vtable to allow the call to be resolvable dynamically; for more information visit <https://doc.rust-lang.org/reference/items/traits.html#object-safety>

How can I make my trait safe and call member functions in threads?

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

trait Record {
    fn run(self: Arc<Self>);
}

struct RecordImg{
    count: u8,
    workers_vec: Mutex<Vec<thread::JoinHandle<()>>>,

}

impl RecordImg {
    fn new(count: u8) -> Self {
        Self {
            count,
            workers_vec: Mutex::new(Vec::new()),
        }
    }
    pub fn record_image(self: &Arc<Self>) {
        println!("RecordImg {} imeges", self.count);
    }
}

impl Record for RecordImg {
    fn run(self: Arc<Self>) {
        println!("RecordImg");
        let runner = self.clone();
        let mut workers_vec = self.workers_vec.lock().unwrap();
        workers_vec.push(thread::spawn(move || {
            runner.record_image();
        }));
    }
}

struct RecordCan{
    count: u8,
    workers_vec: Mutex<Vec<thread::JoinHandle<()>>>,
}

impl RecordCan {
    fn new(count: u8) -> Self {
        Self {
            count,
            workers_vec: Mutex::new(Vec::new()),
        }
    }
    
    fn record_can(self: &Arc<Self>) {
        println!("RecordImg {} canmessages", self.count);
    }
}

impl Record for RecordCan {
    fn run(self: Arc<Self>) {
        println!("RecordCan");
        let runner = self.clone();
        let mut workers_vec = self.workers_vec.lock().unwrap();
        workers_vec.push(thread::spawn(move || {
            runner.record_can();
        }));
    }
}

// Function that takes a vector of trait objects
fn perform_recording(recorders: &[Arc<dyn Record>]) {
    for recorder in recorders {
        Arc::clone(recorder).run();
    }
}

fn main() {
    // Create instances of Record trait objects
    // Create a vector of Record trait objects
    let mut record_vec: Vec<Arc<dyn Record>> = Vec::new();
    record_vec.push(Arc::new(RecordImg::new(1)));
    record_vec.push(Arc::new(RecordCan::new(2)));

    // Perform recording using the vector of Record trait objects
    perform_recording(&record_vec);
}

Rust Playground

Note that the program will then, as it stands now, often reach the end of the main thread and terminate before the spawned threads could finish; so you’re still missing code to e.g. wait for the workers to finish.

1 Like

It solved it, Thank you so much.
It's pseudocode, in my original code I have sleep and timeout before stoping.

Can you please explain why changing from &Ark<Self> -> Ark<Self> and Vec<Box<dyn Record>> -> Vec<Arc<dyn Record>> solved it?

Trait object’s implementation does not permit more than one level of indirection. You create a trait object by going from a “pointer to Foo” kind of type to a “pointer to dyn Trait” kind of type. When calling a trait object’s method, it will use a vtable to get the right implementation, and also turn the function argument back into the “pointer to Foo” kind of type.

E.g. you can convert &Foo to &dyn Tait (if Foo: Trait), and the method implementation would then receive a &Foo again. This works by adding a vtable to make a fat pointer &dyn Tait. The same thing can work for other pointer types like Box<Foo> to Box<dyn Trait> or Arc<Foo> to Arc<dyn Trait>.

But &Arc<Foo> to &Arc<dyn Trait> doesn’t work; it would require the vtable to be added behind some shared reference, you can’t do that. Similarly, the other way for implementing the method doesn’t work either. This kind of argument is the reason why trait methods for traits supporting dyn Trait are limited in their signature. The Self type mustn’t appear outside of self, and self itself can also only be of a handful of (smart) pointer types, and without double indirection. That’s why self: &Arc<Self> can’t work here. You can read the precise rules here.

The change from Vec<Box<dyn Record>> to Vec<Arc<dyn Record>> was only a secondary fix; after all, we need to create the Arc<dyn Record> somehow, somewhere, and you can’t get that anymore once you’ve made your value into Box<dyn Record>; you’ll need to start with Arc<RecordImg> and Arc<RecordCan> and turn those into Arc<dyn Record> directly.


The self: Arc<Self> approach has the (very minor) downside that you’ll have to clone the Arc to call it. On the other hand, for your code specifically, it also means you can just remove the let runner = self.clone() step.

1 Like

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.