8

I tried to create a HashMap with functions as the values:

#[macro_use]
extern crate lazy_static;

use std::collections::HashMap;

lazy_static! {
    static ref HASHES: HashMap<&'static str, &'static Fn([u8])> = {
        let mut m = HashMap::new();
        m.insert("md5", &md5);
        m
    };
}

fn md5(bytes: &[u8]) -> String {
    String::default()
}

The compiler gives me an error:

error[E0277]: the trait bound `std::ops::Fn([u8]) + 'static: std::marker::Sync` is not satisfied in `&'static std::ops::Fn([u8]) + 'static`
  --> src/main.rs:6:1
   |
6  |   lazy_static! {
   |  _^ starting here...
7  | |     static ref HASHES: HashMap<&'static str, &'static Fn([u8])> = {
8  | |         let mut m = HashMap::new();
9  | |         m.insert("md5", &md5);
10 | |         m
11 | |     };
12 | | }
   | |_^ ...ending here: within `&'static std::ops::Fn([u8]) + 'static`, the trait `std::marker::Sync` is not implemented for `std::ops::Fn([u8]) + 'static`
   |
   = note: `std::ops::Fn([u8]) + 'static` cannot be shared between threads safely
   = note: required because it appears within the type `&'static std::ops::Fn([u8]) + 'static`
   = note: required because of the requirements on the impl of `std::marker::Sync` for `std::collections::hash::table::RawTable<&'static str, &'static std::ops::Fn([u8]) + 'static>`
   = note: required because it appears within the type `std::collections::HashMap<&'static str, &'static std::ops::Fn([u8]) + 'static>`
   = note: required by `lazy_static::lazy::Lazy`
   = note: this error originates in a macro outside of the current crate

I don't understand what should I do to fix this error and I don't know any other way of creating such a HashMap.

1 Answer 1

11

Your code has multiple issues. The error presented by the compiler is telling you that your code, will allow memory unsafety:

`std::ops::Fn([u8]) + 'static` cannot be shared between threads safely

The type you are storing in your HashMap has no guarantee that it can be shared.

You can "fix" that by specifying such a bound by changing your value type to &'static (Fn([u8]) + Sync). This unlocks the next error, due to the fact that your function signatures don't match up:

expected type `std::collections::HashMap<&'static str, &'static std::ops::Fn([u8]) + std::marker::Sync + 'static>`
   found type `std::collections::HashMap<&str, &fn(&[u8]) -> std::string::String {md5}>`

"Fixing" that with &'static (Fn(&[u8]) -> String + Sync) leads to esoteric higher-kinded lifetime errors:

expected type `std::collections::HashMap<&'static str, &'static for<'r> std::ops::Fn(&'r [u8]) -> std::string::String + std::marker::Sync + 'static>`
   found type `std::collections::HashMap<&str, &fn(&[u8]) -> std::string::String {md5}>`

Which can be "fixed" by casting the function with &md5 as &'static (Fn(&[u8]) -> String + Sync)), which leads to

note: borrowed value must be valid for the static lifetime...
note: consider using a `let` binding to increase its lifetime

This bottoms out because the reference you've made is to a temporary value that doesn't live outside of the scope.


I put fix in scare quotes because this isn't really the right solution. The right thing is to just use a function pointer:

lazy_static! {
    static ref HASHES: HashMap<&'static str, fn(&[u8]) -> String> = {
        let mut m = HashMap::new();
        m.insert("md5", md5 as fn(&[u8]) -> std::string::String);
        m
    };
}

Honestly, I'd say that a HashMap is probably overkill; I'd use an array. A small array is probably faster than a small HashMap:

type HashFn = fn(&[u8]) -> String;

static HASHES: &'static [(&'static str, HashFn)] = &[
    ("md5", md5),
];

You can start by just iterating through the list, or maybe be fancy and alphabetize it and then use binary_search when it gets a bit bigger.

Sign up to request clarification or add additional context in comments.

2 Comments

Is there a rule of thumb to decide between a HashMap and an array of two-tuples? Is it dependent on the collection size? And/or whether we know the size in advance?
@ArmandoPérezMarqués I always recommend benchmarking if it's important. You certainly would need to know the size in advance to create an array, but beyond that I'd say it's "a slice of 2-tuples is OK for a 'small' number of values".

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.