У меня есть структура с двумя параметрами типа, один из которых имеет тип по умолчанию:
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
? Использует ли хэш-карта какую-то магию компилятора?
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 во что-то другое, это станет ясно из сообщения об ошибке.
Но это означает, что вы не можете называть new
или foo
вместо F = i32
, верно? Возможно ли это достичь?
@hellow Правильно, если new()
определено только для F = ()
, вы не можете вызывать его для других типов F
. Если вы хотите разрешить и это, вам нужно добавить еще один более гибкий метод с другим именем или использовать Default::default()
, как это делает HashMap
.
Даже
let foo: Foo<_> = Foo::new();
работает нормально, так какT
можно вывести. Это выглядит так, как будто не предоставляет никакой дополнительной информации, но на самом деле это так — явное опускание второго параметра в конструкторе типа указывает компилятору использовать значение по умолчанию. Код без какой-либо аннотации типа ведет себя какlet foo: Foo<_, _> = Foo::new();
, который завершается с той же ошибкой.