Avoiding `Sync` for shared static variables

Viewed 1050

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.

1 Answers

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<_>;
Related