На самом деле здесь можно применить один трюк.
Чтобы компилятор выбрал impl
для вас, он должен быть прикрепленк параметру типа, который может быть выведен.Вы можете добавить параметр типа в trait Foo
и создать структуры маркеров, чтобы impl
больше не перекрывались:
trait Foo<K, U> {
const FOO: i32;
}
struct ByKeyInto;
impl<K, K_, V> Foo<HashMap<K_, V>, ByKeyInto> for HashMap<K, V>
where
K: Hash + Eq + Into<K_>,
{
const FOO: i32 = 1;
}
struct ByValInto;
impl<K, V, V_> Foo<HashMap<K, V_>, ByValInto> for HashMap<K, V>
where
K: Hash + Eq,
V: Into<V_>,
{
const FOO: i32 = 2;
}
Поскольку Foo<_, ByKeyInto>
и Foo<_, ByValInto>
- это разные черты, impl
больше не перекрываются.Когда вы используете обобщенную функцию, для которой требуется Foo<_, U>
для некоторых U
, компилятор может искать тип, который работает, и он разрешает преобразовывать в конкретный тип, если есть только одна возможность.
Вот пример кода, который компилирует и выводит правильные значения impl
на каждом сайте вызовов, выбирая ByKeyInto
или ByValInto
для U
:
fn call_me<T, U>(_: T)
where
T: Foo<HashMap<String, i32>, U>,
{
println!("{}", T::FOO);
}
fn main() {
let x: HashMap<&str, i32> = HashMap::new();
call_me(x);
let y: HashMap<String, bool> = HashMap::new();
call_me(y);
}
Это печатает ( детская площадка ):
1
2
Однако, поскольку Into
является рефлексивным (то есть T
реализует Into<T>
для всех T
), это неудобноесли вы хотите использовать Foo<HashMap<K, V>>
для HashMap<K, V>
.Так как перекрываются impl
с в этом случае, вы должны выбрать один из них turboish (::<>
).
let z: HashMap<String, i32> = HashMap::new();
call_me::<_, ByKeyInto>(z); // prints 1
call_me::<_, ByValInto>(z); // prints 2