Different but similar ways of creating a hashmap

Viewed 70

The following code shows some ways of creating a HashMap.

use std::collections::HashMap;

fn main() {
    let h1 : HashMap<usize, (fn(usize) -> usize, usize, usize)> = HashMap::new();
    h1.insert(1, (|n| n, 1, 1)); // works fine
    let h2 : HashMap<usize, (fn(usize) -> usize, usize, usize)> = HashMap::from([(1, (|n| n, 1, 1))]); // error
    HashMap::<usize, (fn(usize) -> usize, usize, usize)>::from([(1, (|n| n, 1, 1))]); // error
}

These seems to essentially express the same thing, but the Rust compiler disagrees (playground)!

error[E0308]: mismatched types
  --> src/main.rs:23:67
   |
23 |     let h2 : HashMap<usize, (fn(usize) -> usize, usize, usize)> = HashMap::from([(1, (|n| n, 1, 1))]);
   |              --------------------------------------------------   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected fn pointer, found closure
   |              |
   |              expected due to this
   |
   = note: expected struct `HashMap<usize, (fn(usize) -> usize, usize, usize)>`
              found struct `HashMap<{integer}, ([closure@src/main.rs:23:87: 23:92], {integer}, {integer})>`

error[E0277]: the trait bound `HashMap<usize, (fn(usize) -> usize, usize, usize)>: From<[({integer}, ([closure@src/main.rs:24:70: 24:75], {integer}, {integer})); 1]>` is not satisfied
   --> src/main.rs:24:5
    |
24  |     HashMap::<usize, (fn(usize) -> usize, usize, usize)>::from([(1, (|n| n, 1, 1))]);
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `From<[({integer}, ([closure@src/main.rs:24:70: 24:75], {integer}, {integer})); 1]>` is not implemented for `HashMap<usize, (fn(usize) -> usize, usize, usize)>`
    |
    = help: the following implementations were found:
              <HashMap<K, V> as From<[(K, V); N]>>
note: required by `from`

Why do I get these errors for way 2 and 3, but not for the first way?

1 Answers

fn(usize) -> usize is a function pointer and |n| n is a closure. Closures that don't capture environment variables can be automatically converted to function pointers, which happens in this line

h1.insert(1, (|n| n, 1, 1));

More information about the distinction between function pointers and closures can be found in this question and answer

For some reason, the automatic conversion is not applied in the second and third cases but the compiler can be coerced into doing so by typing (|n| n) as _ or (|n| n) as fn(usize) -> usize.

If you want to store closures that capture variables (instead of function pointers), you have to put them in a Box similar to this:

let some = "hello".to_string();
let mut h1 : HashMap<usize, Box<dyn Fn(usize) -> usize>> = HashMap::new();
h1.insert(1, Box::new(move |_n| some.len())); // 'some' is moved here
Related