我可以避免使用泛型来解决 trait 实现的急切歧义吗? [英] Can I avoid eager ambiguity resolution for trait implementations with generics?

查看:16
本文介绍了我可以避免使用泛型来解决 trait 实现的急切歧义吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

考虑以下 Rust 代码 [游乐场]:

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() {}

(const 不相关,我希望代码也能用 fns 编译).

(The const is not relevant, I'd like the code to compile with fns too).

编译失败,报错:

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:

  1. 上面的代码(或一些变通方法)应该可以正常编译.
  2. 在调用站点,如果给定类型只有一种 impl 可能,则选择该类型.
  3. 在调用站点,如果有多个impl可能,那么就是一个错误(一致性问题).
  1. The above code (or some work around) should compile fine.
  2. At the call site, if there is only one impl possible for the given type, then that one is picked.
  3. At the call site, if there are multiple impls 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?

推荐答案

事实上,您可以在这里应用一个技巧.

There is, in fact, a trick you may be able to apply here.

为了让编译器为您选择一个impl,它必须附加到一个可以推断的类型参数.您可以向 trait Foo 添加类型参数并创建标记结构,以便 impl 不再重叠:

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 to trait Foo and create marker structs so that the impls no longer overlap:

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 时,编译器可以去寻找一种有效的类型,它确实 如果可证明只有一种可能性,则解析为具体类型.

Since Foo<_, ByKeyInto> and Foo<_, ByValInto> are different traits, the impls no longer overlap. When you use a generic function that requires Foo<_, U> for some U, 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.

这是一个代码示例,它通过为 U 选择 ByKeyIntoByValInto 在每个调用站点编译和推断正确的 impl:

Here's an example of code that compiles and infers the correct impl at each call site by picking ByKeyInto or ByValInto for 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

This prints (playground):

1
2

<小时>

然而,由于 Into 是自反的(也就是说,T 为所有 T 实现了 Into),如果你想对 HashMap 使用 Foo<HashMap<K, V>> 这很尴尬.由于在这种情况下 重叠了 impl,您必须通过 turbofish (::<>) 选择一个.


However, since Into is reflexive (that is, T implements Into<T> for all T), this is awkward if you want to use Foo<HashMap<K, V>> for HashMap<K, V>. Since there are overlapping impls in this case, you have to choose one by turbofish (::<>).

let z: HashMap<String, i32> = HashMap::new();
call_me::<_, ByKeyInto>(z);  // prints 1
call_me::<_, ByValInto>(z);  // prints 2

这篇关于我可以避免使用泛型来解决 trait 实现的急切歧义吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆