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 testTraitinvariant 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
mainfunction 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
implinstead ofdynas this situation is too complex for trait objects - No need to use
unsafeanywhere; in fact, you should never defineSendandSyncmanually, as they are derived automatically
