HashMap::from() with closures as values?

Is there a way to make the following work? Thanks!

Given that each closure has its own type, I don’t see how (apart from creating a HashMap first and inserting).

pub static LANGUAGES: LazyLock<HashMap<&'static str, fn() -> &'static str>> = LazyLock::new(|| {
    HashMap::<&'static str, fn() -> &'static str>::from([
        ("source.abap", || {
            include_str!("languages/abap.tmLanguage.json")
        }),
        ("source.actionscript.3", || {
            include_str!("languages/actionscript-3.tmLanguage.json")
        }),
        ("source.ada", || {
            include_str!("languages/ada.tmLanguage.json")
        }),
    ])
});

This does work:

let f: fn() -> &'static str = || include_str!("languages/abap.tmLanguage.json");

You can manually coerce it into function pointers.

pub static LANGUAGES: LazyLock<HashMap<&'static str, fn() -> &'static str>> = LazyLock::new(|| {
    HashMap::<&'static str, fn() -> &'static str>::from([
-       ("source.abap", || {
-           include_str!("languages/abap.tmLanguage.json")
-       }),
+       ("source.abap", (|| {
+           include_str!("languages/abap.tmLanguage.json")
+       }) as fn() -> &'static str ),
        ("source.actionscript.3", || {
            include_str!("languages/actionscript-3.tmLanguage.json")
        }),
        ("source.ada", || {
            include_str!("languages/ada.tmLanguage.json")
        }),
    ])
});
1 Like

you are correct that each closure has its own type. they can, however, coerce to function types if they are captureless. but coersion only happens if the compiler can figure out the type. unfortunately, the From::from() doesn't provide context in this case. you can explicitly annotate the type, although it is quite verbose because you must specify the length of the array type.

// option1: annotate with named binding
let map_entries: [(&str, fn()->&'static str); 3] = [...];
return HashMap::from(map_entries);
// option 2: annotate with explicit argument for `From` trait
return From::< [(&str, fn()->&'static str); 3] >::from([...]);

another method to provide context for type inference is to use a helper function, like this:

fn new_hashmap_from<const N: usize>(
    entries: [(&'static str, fn() -> &'static str); N],
) -> HashMap<&'static str, fn() -> &'static str> {
    HashMap::from(entries)
}

return new_hash_map_from([...])

1 Like

fn() -> &'static str seems suspicious anyway. Is it just for simplified code for the question? If not, why don't you use HashMap<&'static str, &'static str> instead?

3 Likes

Good point! I initially wanted to load files on demand. But with include_str!(), there is very little overhead, so functions are probably not needed.

if you can include_str!(), that's convinient, but there are rare situations you don't want include_str!() (or include_bytes() for that matter) but prefer Box::leak(), for instance, due to some API limitations, or because the data is HUGE, or because the data cannot baked into the binary and might be edited outside your program.

that being said, &'static str should be suffient most of the time, though a fn can be a valid use case if laziness does make sense (for instance the potential data set is huge but only sparsely used each run)

1 Like

include_str! is a macro that resolves to the contents of the given filename at compile time. Putting it behind a closure is completely equivalent to just writing || "long string that is present in the file", which serves no apparent purpose.
If you want to read the strings at runtime, use || std::fs::read_to_string(filename) (or one of the Lazy constructs if you don't want to read it multiple times), else just use plain string literals without closures using include_str!.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.