Reputation: 6258
I have the following definitions:
struct MyCustomFactory;
trait Factory {
fn new<'a>(entity: &'a str) -> Arc<dyn Display + 'a>
where
Self: Sized;
}
impl Factory for MyCustomFactory {
fn new<'a>(entity: &'a str) -> Arc<dyn Display + 'a>
where
Self: Sized,
{
Arc::new(entity)
}
}
Higher level problem:
I'm trying to create a global static (and immutable) map of strings to structs which implement the Factory
trait. The goal is that for any value member of the map, I can treat it like a Factory
, call new
against it, and in turn will get something back that (in this case) implements the Display
trait.
I have the following:
static ITEMS: Lazy<BTreeMap<&'static str, Arc<dyn Factory>>> =
Lazy::new(|| {
let mut map = BTreeMap::new();
let value = Arc::new(MyCustomFactory) as Arc<dyn Factory>;
map.insert("foo", value);
map
});
The compiler complains with:
77 | / static ITEMS: Lazy<BTreeMap<&'static str, Arc<dyn Factory>>> =
78 | | Lazy::new(|| {
79 | | let map = BTreeMap::new();
80 | | let value = Arc::new(MyCustomFactory) as Arc<dyn Factory>;
... |
88 | | });
| |_______^ `(dyn Factory + 'static)` cannot be shared between threads safely
|
= help: the trait `std::marker::Sync` is not implemented for `(dyn Factory + 'static)`
= note: required because of the requirements on the impl of `std::marker::Send` for `std::sync::Arc<(dyn Factory + 'static)>`
= note: required because of the requirements on the impl of `std::marker::Send` for `alloc::collections::btree::node::Root<&'static str, std::sync::Arc<(dyn Factory + 'static)>>`
= note: required because it appears within the type `std::collections::BTreeMap<&'static str, std::sync::Arc<(dyn Factory + 'static)>>`
= note: required because of the requirements on the impl of `std::marker::Sync` for `once_cell::imp::OnceCell<std::collections::BTreeMap<&'static str, std::sync::Arc<(dyn Factory + 'static)>>>`
= note: required because it appears within the type `once_cell::sync::OnceCell<std::collections::BTreeMap<&'static str, std::sync::Arc<(dyn Factory + 'static)>>>`
= note: required because of the requirements on the impl of `std::marker::Sync` for `once_cell::sync::Lazy<std::collections::BTreeMap<&'static str, std::sync::Arc<(dyn Factory + 'static)>>>`
= note: shared static variables must have a type that implements `Sync`
Things I have tried:
Implement Sync
against the trait: Declaring the trait to have trait Factory : Sync
, (meaning all items which implement the trait, must implement Sync
) and/or defining:
|
66 | unsafe impl Sync for dyn Factory {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ can't implement cross-crate trait with a default impl for non-struct/enum type
Wrapping Arc<dyn Factory>
: I've tried using Mutex
/ RefCell
/ OnceCell
and others, all resulting in the same error. It seems like there should be a way to treat the underlying MyCustomFactory
as a Singleton that can be locked around. It's fine (and expected) that's there's only once instance of this struct globally.
Upvotes: 5
Views: 3541
Reputation: 361977
You were close with adding Sync
. If you look closely you'll see that the error then recommends adding Send
:
= help: the trait `std::marker::Send` is not implemented for `(dyn Factory + 'static)`
Add both Send
and Sync
and it compiles. You can do:
trait Factory: Send + Sync {
...
}
Or, my preference, require them specifically in the static map, since that's where they're needed:
static ITEMS: Lazy<BTreeMap<&'static str, Arc<dyn Factory + Send + Sync>>> =
Lazy::new(|| {
let mut map = BTreeMap::new();
let value = Arc::new(MyCustomFactory) as Arc<dyn Factory + Send + Sync>;
map.insert("foo", value);
map
});
You could also reduce duplication by letting it infer the second Arc
:
let value = Arc::new(MyCustomFactory) as Arc<_>;
Upvotes: 2