Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sharing a reference to an instance of trait between threads

Tags:

rust

I am playing with Rust's concurrency and trying to wrap my head around Send/Sync/Arc/Mutex. I have problems with sharing a reference to an instance of trait which is held in a HashMap:

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

#[derive(Debug)]
struct A {
    foo: u8,
}

trait Foo {
    fn get_foo(&self) -> u8;
}

impl Foo for A {
    fn get_foo(&self) -> u8 {
        self.foo
    }
}

fn main() {
    let a = Arc::new(A { foo: 8 });

    let mut map: HashMap<u8, Arc<Foo>> = HashMap::new();
    map.insert(8u8, a);

    for _ in 0..2 {
        let a = map.get(&8u8).expect("boom");
        let a = a.clone();
        thread::spawn(move || {
            let _ = a.get_foo();
        });
    }
    thread::sleep(Duration::from_millis(200));
}

(playground)

It gives me these errors:

error[E0277]: `dyn Foo` cannot be sent between threads safely
  --> src/main.rs:27:9
   |
27 |         thread::spawn(move || {
   |         ^^^^^^^^^^^^^ `dyn Foo` cannot be sent between threads safely
   |
   = help: the trait `std::marker::Send` is not implemented for `dyn Foo`
   = note: required because of the requirements on the impl of `std::marker::Send` for `std::sync::Arc<dyn Foo>`
   = note: required because it appears within the type `[closure@src/main.rs:27:23: 29:10 a:std::sync::Arc<dyn Foo>]`
   = note: required by `std::thread::spawn`

error[E0277]: `dyn Foo` cannot be shared between threads safely
  --> src/main.rs:27:9
   |
27 |         thread::spawn(move || {
   |         ^^^^^^^^^^^^^ `dyn Foo` cannot be shared between threads safely
   |
   = help: the trait `std::marker::Sync` is not implemented for `dyn Foo`
   = note: required because of the requirements on the impl of `std::marker::Send` for `std::sync::Arc<dyn Foo>`
   = note: required because it appears within the type `[closure@src/main.rs:27:23: 29:10 a:std::sync::Arc<dyn Foo>]`
   = note: required by `std::thread::spawn`

Could anyone please recommend an approach for this task? I think I'm kinda stuck with Rust's way to work with traits and threading.

like image 478
franza Avatar asked Sep 03 '15 07:09

franza


People also ask

What is send trait in Rust?

Rust captures this through the Send and Sync traits. A type is Send if it is safe to send it to another thread. A type is Sync if it is safe to share between threads (T is Sync if and only if &T is Send).

What is send in trait?

Send means that a type is safe to move from one thread to another. If the same type also implements Copy , this also means that it is safe to copy from one thread to another. Sync means that a type is safe to reference from multiple threads at the same time.


2 Answers

Remember that types of original values which are converted to trait objects are erased. Therefore, the compiler can't know whether the data inside the Arc<Foo> is Send and Sync, and without these traits sharing data across threads may be unsafe. You need to specify that types which can be stored in Arc<Foo> must be Send and Sync:

let mut map: HashMap<u8, Arc<Foo + Sync + Send>> = HashMap::new();

(try it here)

The Send bound is required by thread::spawn(), and Sync is required by Arc for it to be Send. Additionally, thread::spawn() also requires 'static but it is implicit in this particular Arc<Foo + Sync + Send> type declaration.

Of course, you will be able to store only Sync and Send implementations of Foo, but this is necessary to ensure memory safety. However, in Rust synchronization is implemented with wrappers like Mutex<T> or RwLock<T>. They don't implement Foo even if T implements Foo, therefore you won't be able to store, say, Mutex<Foo + Send> inside your map (unless Foo is your trait and you implemented it for Mutex<Foo>, which could be unwieldy), which would be necessary if your Foo implementations are not Sync but Send (though I'm not sure I can provide an example of such type now).

To solve this you'd need to change map type to contain a mutex inside it explicitly:

let mut map: HashMap<u8, Arc<Mutex<Foo + Send>>> = HashMap::new();

This way, there is no need for the Sync bound because Mutex is Sync if its contents are Send.

And naturally, you won't be able to share Foo implementations which are not Send at all, and there is no way around it. This can happen, for example, if Foo's implementation contains Rcs.

like image 175
Vladimir Matveev Avatar answered Oct 08 '22 21:10

Vladimir Matveev


Vladimir gives one solution to your problem in the first half of his answer: tell Rust that your HashMap contains Foos which are Send and Sync. Alternatively, you may change the definition of Foo itself to include these trait bounds:

trait Foo: Sync + Send {
    fn get_foo(&self) -> u8;
}

Since struct A is indeed Send and Sync, and since struct A does indeed implement trait Foo, the type checker will not complain when you use an Arc<A> as an Arc<Foo>.

If instead of sharing immutable (atomically reference counted) references to Foos you wanted to share mutable (atomically reference counted) references to Foos, you need to control access to the Foos. This can be accomplished using e.g. a Mutex. Since the Mutex would then be taking care of the synchronization, the Sync bound on Foo can be dropped. For example:

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

#[derive(Debug)]
struct A {
    foo: u8,
}

trait Foo: Send {
    fn get_foo(&self) -> u8;
}

impl Foo for A {
    fn get_foo(&self) -> u8 {
        self.foo
    }
}

fn main() {
    let a = Arc::new(Mutex::new(A { foo: 8 }));

    let mut map: HashMap<u8, Arc<Mutex<Foo>>> = HashMap::new();
    map.insert(8u8, a);

    for _ in 0..2 {
        let a = map.get(&8u8).expect("boom").clone();
        thread::spawn(move || {
            let result = a.lock().ok().expect("boom indeed").get_foo();
            println!("Result: {}", result);
        });
    }
    thread::sleep(Duration::from_millis(200));
}

(playground)

like image 12
Thierry Avatar answered Oct 08 '22 20:10

Thierry