Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create a lazy_static HashMap with function references as value?

Tags:

rust

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.

like image 299
Victor Polevoy Avatar asked Apr 09 '17 13:04

Victor Polevoy


1 Answers

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.

like image 53
Shepmaster Avatar answered Oct 16 '22 21:10

Shepmaster