Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does type inference behave differently for `HashMap` and my own struct in the presence of a type parameter default?

Tags:

rust

I have a struct with two type parameters, one of which has a default type:

use std::marker::PhantomData;

struct Foo<T, F = ()>(PhantomData<(T, F)>);

impl<T, F> Foo<T, F> {
    fn new() -> Self { Self(PhantomData) }
    fn foo(&self, _: T) {}
}


let foo = Foo::new();
foo.foo(0u32);

The code above leads to:

error[E0282]: type annotations needed
  --> src/main.rs:17:15
   |
17 |     let foo = Foo::new();
   |         ---   ^^^^^^^^ cannot infer type for `F`
   |         |
   |         consider giving `foo` a type

I don't understand why the default type is not used here. Note that saying let foo: Foo<u32> = Foo::new(); already works -- so there is no need to specify the parameter F. But why specify T? So I was already confused.

But then I remembered that all of this works with HashMap! It is defined as struct HashMap<K, V, S = RandomState>. And I never needed to specify anything. For example, this works:

use std::collections::HashMap;

let mut map = HashMap::new();
map.insert(0u32, 'x');

(Everything on Playground)

Why is the default type/inference behavior different between Foo and HashMap? Does the hashmap use some compiler magic?

like image 857
Lukas Kalbertodt Avatar asked Apr 10 '19 15:04

Lukas Kalbertodt


1 Answers

HashMap::new is defined this way:

impl<K: Hash + Eq, V> HashMap<K, V, RandomState> {
    pub fn new() -> HashMap<K, V, RandomState> {
        Default::default()
    }
}

RandomState is provided for S for new. Your code would look like this for the same behavior:

impl<T> Foo<T, ()> {
    fn new() -> Self { Self(PhantomData) }
    fn foo(&self, _: T) {}
}

Playground

Note: Default can be used for a custom BuildHasher:

impl<K, V, S> Default for HashMap<K, V, S>
like image 79
Tim Diekmann Avatar answered Oct 31 '22 09:10

Tim Diekmann