我可以避免急于解决使用泛型的特征实现的歧义吗?

时间:2018-09-11 17:17:46

标签: generics rust traits

请考虑以下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<_, _>`

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

  1. 上面的代码(或解决方法)应该可以编译。
  2. 在呼叫站点,如果给定类型只有一种impl,那么就选择那一种。
  3. 在呼叫站点,如果可能有多个impl,则表示错误(一致性问题)。

更简洁地说,我想在呼叫站点而不是在定义站点进行歧义解决。可能有这种行为吗?

2 个答案:

答案 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选择ByKeyIntoByValInto在每个呼叫站点编译并推断正确的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