请考虑以下Rust代码[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() {}
(const
不相关,我也希望代码也可以与fn
一起编译)。
它无法编译并显示错误:
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
,则表示错误(一致性问题)。更简洁地说,我想在呼叫站点而不是在定义站点进行歧义解决。可能有这种行为吗?
答案 0 :(得分:2)
对于使用泛型的特征实现,我可以避免急切的歧义解决方案吗?
否。
是否可以在呼叫站点而不是在定义站点进行[歧义解决]?
否。
有一个(延迟的)RFC for specialization,它允许重叠的特征实现,但前提是其中一个比另一个更具体。我不认为您的情况如此,因此无济于事。
另请参阅:
答案 1 :(得分:1)
实际上,您可以在此处应用技巧。
为了使编译器为您 pick 一个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);
}
此打印(playground):
1
2
但是,由于Into
是自反的(也就是说,T
为所有Into<T>
实现了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