我是否可以避免使用泛型实现的特质实现的过度歧义解决?



考虑以下 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不相关,我也希望代码也使用fns 编译(。

它编译失败并显示错误:

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<_, _>`

据我了解,问题是这里存在歧义 - 如果两者都合法,应该选择哪种实现?理想情况下,我希望有以下内容:

  1. 上面的代码(或一些变通方法(应该可以很好地编译。
  2. 在调用站点,如果给定类型只有一个可能的impl,则选择该。
  3. 在呼叫站点,如果可能有多个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的泛型函数时,编译器可以寻找有效的类型,如果只有一种可能性,它确实会解析为具体类型。

下面是一个代码示例,该代码通过选取ByKeyIntoByValInto来编译和推断每个调用站点的正确implU

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实现所有TInto<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

最新更新