Sharing a reference to an instance of trait between threads

后端 未结 2 1117
北荒
北荒 2021-01-01 19:24

I am playing with Rust\'s concurrency and trying to wrap my head around Send/Sync/Arc/Mutex. I have problems with sharing

相关标签:
2条回答
  • 2021-01-01 20:18

    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)

    0 讨论(0)
  • 2021-01-01 20:21

    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.

    0 讨论(0)
提交回复
热议问题