考虑以下 Rust 代码 [操场]:
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() {}
(const
不相关,我也希望代码也使用fn
s 编译(。
它编译失败并显示错误:
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<_, _>`
据我了解,问题是这里存在歧义 - 如果两者都合法,应该选择哪种实现?理想情况下,我希望有以下内容:
- 上面的代码(或一些变通方法(应该可以很好地编译。
- 在调用站点,如果给定类型只有一个可能的
impl
,则选择该。 - 在呼叫站点,如果可能有多个
impl
,则为错误(一致性问题(。
更简洁地说,我希望在调用站点而不是在定义站点完成歧义解决。是否有可能出现这种行为?
我是否可以避免使用泛型实现的特质实现的急切歧义解决?
不。
是否可以[在呼叫站点而不是在定义站点进行歧义解决]?
不。
有一个(长期延迟的(专用化RFC,它将允许重叠的特征实现,但前提是其中一个比其他特征更具体。我不相信这对你的情况是正确的,所以这无济于事。
另请参阅:
- Rust 中 trait 的冲突实现
- 为什么我得到没有实现 Ord 的 f32 的"特征冲突实现"?
- 为什么在专门化特征时会出现冲突的实现错误?
- 使用泛型类型时,"From"的实现如何冲突?
事实上,你可以在这里应用一个技巧。
为了让编译器为您选择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
的泛型函数时,编译器可以寻找有效的类型,如果只有一种可能性,它确实会解析为具体类型。
下面是一个代码示例,该代码通过选取ByKeyInto
或ByValInto
来编译和推断每个调用站点的正确impl
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
实现所有T
的Into<T>
(,如果要将Foo<HashMap<K, V>>
用于HashMap<K, V>
,这很尴尬。由于在这种情况下存在重叠的impl
,因此您必须通过涡轮鱼(::<>
(选择一个。
let z: HashMap<String, i32> = HashMap::new();
call_me::<_, ByKeyInto>(z); // prints 1
call_me::<_, ByValInto>(z); // prints 2