Sharing a reference to an instance of trait betwee

2019-02-16 23:39发布

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.

标签: rust
2条回答
闹够了就滚
2楼-- · 2019-02-17 00:00

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)

查看更多
我命由我不由天
3楼-- · 2019-02-17 00:01

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.

查看更多
登录 后发表回答