Consider the following Rust code [playground]:
use std::collections::HashMap;
use std::hash::Hash;
trait Foo<K> {
const FOO: i32;
}
impl<K, K_, V> Foo<HashMap<K_, V>> for HashMap<K, V>
where
K: Hash + Eq + Into<K_>,
{
const FOO: i32 = 1;
}
impl<K, V, V_> Foo<HashMap<K, V_>> for HashMap<K, V>
where
K: Hash + Eq,
V: Into<V_>,
{
const FOO: i32 = 2;
}
fn main() {}
(The const
is not relevant, I'd like the code to compile with fn
s too).
It fails to compile with the error:
error[E0119]: conflicting implementations of trait `Foo<std::collections::HashMap<_, _>>` for type `std::collections::HashMap<_, _>`:
--> src/main.rs:15:1
|
8 | / impl<K, K_, V> Foo<HashMap<K_, V>> for HashMap<K, V>
9 | | where
10 | | K: Hash + Eq + Into<K_>,
11 | | {
12 | | const FOO: i32 = 1;
13 | | }
| |_- first implementation here
14 |
15 | / impl<K, V, V_> Foo<HashMap<K, V_>> for HashMap<K, V>
16 | | where
17 | | K: Hash + Eq,
18 | | V: Into<V_>,
19 | | {
20 | | const FOO: i32 = 2;
21 | | }
| |_^ conflicting implementation for `std::collections::HashMap<_, _>`
As I understand it, the problem is that there is an ambiguity here - which implementation should be picked if both are legal? Ideally I'd like to have the following:
- The above code (or some work around) should compile fine.
- At the call site, if there is only one
impl
possible for the given type, then that one is picked. - At the call site, if there are multiple
impl
s possible, then it is an error (coherence issues).
More succinctly, I want ambiguity resolution to be done at the call site, rather than at the definition site. Is it possible to have this behavior?
No.
No.
There's a (long-delayed) RFC for specialization that will allow overlapping trait implementations, but only when one of them is more specific than the others. I don't believe this is true for your case, so it would not help.
See also:
There is, in fact, a trick you may be able to apply here.
In order for the compiler to pick an
impl
for you, it has to be attached to a type parameter that can be inferred. You can add a type parameter totrait Foo
and create marker structs so that theimpl
s no longer overlap:Since
Foo<_, ByKeyInto>
andFoo<_, ByValInto>
are different traits, theimpl
s no longer overlap. When you use a generic function that requiresFoo<_, U>
for someU
, the compiler can go looking for a type that works, and it does resolve to a concrete type if there is provably only one possibility.Here's an example of code that compiles and infers the correct
impl
at each call site by pickingByKeyInto
orByValInto
forU
:This prints (playground):
However, since
Into
is reflexive (that is,T
implementsInto<T>
for allT
), this is awkward if you want to useFoo<HashMap<K, V>>
forHashMap<K, V>
. Since there are overlappingimpl
s in this case, you have to choose one by turbofish (::<>
).