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 structMutex<T>
is invariant over the parameterT
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 usingstd::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 ofdyn
as this situation is too complex for trait objects - No need to use
unsafe
anywhere; in fact, you should never defineSend
andSync
manually, as they are derived automatically