Почему вывод типа ведет себя по-разному для `HashMap` и моей собственной структуры при наличии параметра типа по умолчанию?

У меня есть структура с двумя параметрами типа, один из которых имеет тип по умолчанию:

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);

Приведенный выше код приводит к:

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

Я не понимаю, почему здесь не используется тип по умолчанию. Обратите внимание, что фраза let foo: Foo<u32> = Foo::new(); уже работает, поэтому нет необходимости указывать параметр F. Но зачем указывать T? Так что я уже запутался.

Но потом я вспомнил, что все это работает с HashMap! Он определяется как struct HashMap<K, V, S = RandomState>. И мне никогда не нужно было ничего уточнять. Например, это работает:

use std::collections::HashMap;

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

(Все на детской площадке)

Почему поведение типа/вывода по умолчанию отличается для Foo и HashMap? Использует ли хэш-карта какую-то магию компилятора?

Даже let foo: Foo<_> = Foo::new(); работает нормально, так как T можно вывести. Это выглядит так, как будто не предоставляет никакой дополнительной информации, но на самом деле это так — явное опускание второго параметра в конструкторе типа указывает компилятору использовать значение по умолчанию. Код без какой-либо аннотации типа ведет себя как let foo: Foo<_, _> = Foo::new();, который завершается с той же ошибкой.

Sven Marnach 10.04.2019 22:09
Почему Python в конце концов умрет
Почему Python в конце концов умрет
Последние 20 лет были действительно хорошими для Python. Он прошел путь от "просто языка сценариев" до основного языка, используемого для написания...
7
1
873
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

HashMap::new определяется следующим образом:

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

RandomState предоставляется для S для new. Ваш код будет выглядеть так для того же поведения:

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

Игровая площадка

Примечание: Default можно использовать для пользовательского BuildHasher:

impl<K, V, S> Default for HashMap<K, V, S>

Короче говоря: F, который нельзя вывести, — это параметр F реализации, а не параметр F структуры. Если вы переименуете параметр impl во что-то другое, это станет ясно из сообщения об ошибке.

Sven Marnach 10.04.2019 22:04

Но это означает, что вы не можете называть new или foo вместо F = i32, верно? Возможно ли это достичь?

hellow 11.04.2019 07:59

@hellow Правильно, если new() определено только для F = (), вы не можете вызывать его для других типов F. Если вы хотите разрешить и это, вам нужно добавить еще один более гибкий метод с другим именем или использовать Default::default(), как это делает HashMap.

Sven Marnach 11.04.2019 08:51

Другие вопросы по теме