Home > Back-end >  Passing an object to a thread in rust
Passing an object to a thread in rust

Time:12-23

I'm used to writing code in python and C and try to get along with rust. I want to pass an object to a thread and call a method of this object. In addition, the object is passed to the thread by dependency injection because I aim to reuse this module. When the function expects the Object FIFO, everything's fine. But when using the trait, it fails.

I get the following error when passing the clone of the object to the thread:

borrowed data escapes outside of function requirement occurs because of the type Mutex<&dyn testTrait>, which makes the generic argument &dyn testTrait invariant the struct Mutex<T> is invariant over the parameter T

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

pub trait testTrait: Send   Sync {
    fn test(&self, i: i32) -> i32;
}

pub struct FIFO {}

unsafe impl Send for FIFO {}
unsafe impl Sync for FIFO {}

impl testTrait for FIFO {
    fn test(&self, i: i32) -> i32 {
        return i;
    }
}

impl FIFO {}

fn main() {
    let fifo = FIFO {};
    caller(&fifo);
}

pub fn caller(t: &dyn testTrait) {
    let a = Arc::new(Mutex::new(t));
    let clone = a.clone();
    thread::spawn(move || {
        if let Ok(mut x) = clone.lock() {
            x.test(5);
        }
    });
}

CodePudding user response:

Using a reference in this situation is probably the wrong choice, because a reference connects the lifetime of the thread with the calling function.

This problem is not specific to Rust, Rust just complains about it because Rust has a zero-undefined-behavior tolerance.

In C , for example, it is undefined behavior:

#include <iostream>
#include <thread>
#include <chrono>

struct TestTrait {
  virtual int test() = 0;
};

struct Foo : TestTrait {
  int value;
  int test() override { return value; }
};

int main() {
  {
    Foo x;
    x.value = 10;

    std::thread thrd([&x]() {
      std::cout << x.test() << std::endl;
    });
    thrd.detach();
  }

  std::this_thread::sleep_for(std::chrono::milliseconds(100));

  return 0;
}
Segmentation fault

So as @PitaJ already points out, there are two solutions:

  • Make sure the thread doesn't outlive the main function by using std::thread::scope
  • Make the data reference counted via Arc

I assume you want to go the Arc route because you already started with that. You can't place the reference in the Arc, though, you have to put the object itself into the Arc. The whole point of an Arc is to have multiple ownership, and for that, the Arc has to actually own the object, not borrow it.

Here is a possible solution with Arc:

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

pub trait TestTrait: Send   Sync {
    fn test(&mut self, i: i32) -> i32;
}

pub struct FIFO {}

impl TestTrait for FIFO {
    fn test(&mut self, i: i32) -> i32 {
        return i;
    }
}

impl FIFO {}

fn main() {
    let fifo = Arc::new(Mutex::new(FIFO {}));
    caller(Arc::clone(&fifo));
    std::thread::sleep(Duration::from_millis(100));
}

pub fn caller(t: Arc<Mutex<impl TestTrait   'static>>) {
    thread::spawn(move || {
        if let Ok(mut x) = t.lock() {
            println!("{}", x.test(5));
        }
    });
}
5

Note that:

  • You need to use impl instead of dyn as this situation is too complex for trait objects
  • No need to use unsafe anywhere; in fact, you should never define Send and Sync manually, as they are derived automatically
  • Related