1

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);
        }
    });
}
5
  • 5
    You make a Mutex around a reference, but that doesn't extend the lifetime of the reference. The thread can still outlive the reference since the thread can outlive the function body. You'll either need to change it to a Box<dyn testTrait> parameter or use std::thread::scope Commented Dec 21, 2022 at 21:04
  • 2
    unsafe impl Send for FIFO is a bad sign. That should not be necessary. Those are derived automatically if your struct is fine. If it isn't, don't just make Rust assume it is. Don't bust out unsafe to solve problems. You can probably go your whole career without needing to do that if you do things the Rust way. Commented Dec 21, 2022 at 21:38
  • 1
    Tip: Pass in Arc<Mutex<Box<dyn X>> if you want read-write access, or just Box<dyn X> if it's strictly read-only. Commented Dec 21, 2022 at 21:39
  • @PitaJ You mean the Arc. Commented Dec 22, 2022 at 1:24
  • 2
    @tadman Those unsafe impls are also completely unecessary (the code wotks without them). Commented Dec 22, 2022 at 1:25

1 Answer 1

2

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
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.