I am playing with Rust\'s concurrency and trying to wrap my head around Send
/Sync
/Arc
/Mutex
. I have problems with sharing
Vladimir gives one solution to your problem in the first half of his answer: tell Rust that your HashMap
contains Foo
s 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 Foo
s you wanted to share mutable (atomically reference counted) references to Foo
s, you need to control access to the Foo
s. 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)
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 Rc
s.