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));
}
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.
Vladimir gives one solution to your problem in the first half of his answer: tell Rust that your
HashMap
containsFoo
s which areSend
andSync
. Alternatively, you may change the definition ofFoo
itself to include these trait bounds:Since
struct A
is indeedSend
andSync
, and sincestruct A
does indeed implementtrait Foo
, the type checker will not complain when you use anArc<A>
as anArc<Foo>
.If instead of sharing immutable (atomically reference counted) references to
Foo
s you wanted to share mutable (atomically reference counted) references toFoo
s, you need to control access to theFoo
s. This can be accomplished using e.g. aMutex
. Since theMutex
would then be taking care of the synchronization, theSync
bound onFoo
can be dropped. For example:(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>
isSend
andSync
, and without these traits sharing data across threads may be unsafe. You need to specify that types which can be stored inArc<Foo>
must beSend
andSync
:(try it here)
The
Send
bound is required bythread::spawn()
, andSync
is required byArc
for it to beSend
. Additionally,thread::spawn()
also requires'static
but it is implicit in this particularArc<Foo + Sync + Send>
type declaration.Of course, you will be able to store only
Sync
andSend
implementations ofFoo
, but this is necessary to ensure memory safety. However, in Rust synchronization is implemented with wrappers likeMutex<T>
orRwLock<T>
. They don't implementFoo
even ifT
implementsFoo
, therefore you won't be able to store, say,Mutex<Foo + Send>
inside your map (unlessFoo
is your trait and you implemented it forMutex<Foo>
, which could be unwieldy), which would be necessary if yourFoo
implementations are notSync
butSend
(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:
This way, there is no need for the
Sync
bound becauseMutex
isSync
if its contents areSend
.And naturally, you won't be able to share
Foo
implementations which are notSend
at all, and there is no way around it. This can happen, for example, ifFoo
's implementation containsRc
s.