James Taylor
James Taylor

Reputation: 6258

Avoiding `Sync` for shared static variables

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:

Upvotes: 5

Views: 3541

Answers (1)

John Kugelman
John Kugelman

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

Related Questions