How to create a Map<&'static str, &dyn Fn([u32; 6]) -> String> with phf?

I am trying to create a static Map<&'static str, &dyn Fn([u32; 6]) -> String> with phf crate, code as follow:

pub mod c2i {

    fn add(args: [u32; 6]) -> String {
        format!("add ${}, ${}, ${}", args[2], args[0], args[1])
    }

    fn addi(args: [u32; 6]) -> String {
        format!("addi ${}, ${}, {:04x}", args[1], args[0], args[4])
    }

    static INST_MAP: phf::Map<&'static str, &dyn Fn([u32; 6]) -> String> = phf::phf_map! {
        "001000"  => &addi,
        "_100000" => &add,
    };
}

but it has error:

error[E0277]: `dyn std::ops::Fn([u32; 6]) -> std::string::String` cannot be shared between threads safely
  --> src/util.rs:13:5
   |
13 | /     static INST_MAP: phf::Map<&'static str, &dyn Fn([u32; 6]) -> String> = phf::phf_map! {
14 | |         "001000"  => &addi,
15 | |         "_100000" => &add,
16 | |     };
   | |______^ `dyn std::ops::Fn([u32; 6]) -> std::string::String` cannot be shared between threads safely
   |
   = help: within `phf::map::Map<&str, &dyn std::ops::Fn([u32; 6]) -> std::string::String>`, the trait `std::marker::Sync` is not implemented for `dyn std::ops::Fn([u32; 6]) -> std::string::String`
   = note: required because it appears within the type `&dyn std::ops::Fn([u32; 6]) -> std::string::String`
   = note: required because it appears within the type `(&str, &dyn std::ops::Fn([u32; 6]) -> std::string::String)`
   = note: required because it appears within the type `[(&str, &dyn std::ops::Fn([u32; 6]) -> std::string::String)]`
   = note: required because it appears within the type `&'static [(&str, &dyn std::ops::Fn([u32; 6]) -> std::string::String)]`
   = note: required because it appears within the type `phf::Slice<(&str, &dyn std::ops::Fn([u32; 6]) -> std::string::String)>`
   = note: required because it appears within the type `phf::map::Map<&str, &dyn std::ops::Fn([u32; 6]) -> std::string::String>`
   = note: shared static variables must have a type that implements `Sync`

error: aborting due to previous error;

You can simplify this a bit to use a simpler data structure, showing that it's nothing to do with phf::map:

fn add(args: [u32; 6]) -> String {
    format!("add ${}, ${}, ${}", args[2], args[0], args[1])
}

fn addi(args: [u32; 6]) -> String {
    format!("addi ${}, ${}, {:04x}", args[1], args[0], args[4])
}

static INST: [(&'static str, &dyn Fn([u32; 6]) -> String); 2]
    = [("001000", &addi), ("_100000", &add)];

which still gets a similar error:

trait `std::marker::Sync` is not implemented for `dyn std::ops::Fn([u32; 6]) -> std::string::String`

After a bit of fiddling with the code and reading other type signatures for threading functions, I came up with a type that works:

static INST: [(&'static str, &(dyn Fn([u32; 6]) -> String + Sync)); 2]
    = [("001000", &addi), ("_100000", &add)];

which compiles, at least, and which you should be able to convert back to phf::map::Map.

You should be using the fn(...) -> String type, instead of &dyn Fn(...) -> String.

The fn... type is the type of function pointers of that signature. In a way, they are a shorthand (and implementation-wise, an optimized one) for:

fn (...) -> Ret
~
&'static (dyn Sync + Fn(...) -> Ret)
2 Likes

In generic contexts types don't support anything that you did't declare they have to support. So if you haven't said the closure type is safe to use in a global variable (which is accessible from all threads), then it isn't.

And you make generic types thread-safe by adding + Sync + Send to their traits.