在下面的代码中,我定义了一个名为Wrap(局部类型)的 struct 来包装值,以及一个名为WrapOut(局部特征)的特征来提取该值.我在HashMap(外部类型)的键和值上都使用了Wrap.此版本的代码可以按预期运行

use std::collections::HashMap;

struct Wrap<T> {
    val: T
}

trait WrapOut<T> {
    fn wrap_out(self) -> T;
}

impl<T, U> WrapOut<HashMap<T, U>> for HashMap<Wrap<T>, Wrap<U>> 
where T: std::cmp::Eq + std::hash::Hash
{
    fn wrap_out(self) -> HashMap<T, U> {
        println!("both");
        self.into_iter().map(|(k, v)| (k.val, v.val)).collect()
    }
    
}

impl<T, U> WrapOut<HashMap<T, U>> for HashMap<T, Wrap<U>> 
where T: std::cmp::Eq + std::hash::Hash
{
    fn wrap_out(self) -> HashMap<T, U> {
        println!("only val");
        self.into_iter().map(|(k, v)| (k, v.val)).collect()
    }
    
}


impl<T, U> WrapOut<HashMap<T, U>> for HashMap<Wrap<T>, U> 
where T: std::cmp::Eq + std::hash::Hash
{
    fn wrap_out(self) -> HashMap<T, U> {
        println!("only key");
        self.into_iter().map(|(k, v)| (k.val, v)).collect()
    }
    
}

fn main() {
    let m1 = HashMap::<Wrap<u32>, Wrap<u32>>::new();
    let m2 = HashMap::<u32, Wrap<u32>>::new();
    let m3 = HashMap::<Wrap<u32>, u32>::new();

    let m1: HashMap<u32, u32> = m1.wrap_out();
    let m2: HashMap<u32, u32> = m2.wrap_out();
    let m3: HashMap<u32, u32> = m3.wrap_out();
}

当我通过将泛型类型参数更改为关联类型来重写WrapOut时,发生了冲突的实现错误. 我想知道为什么impl<T, U> WrapOut for HashMap<Wrap<T>, U>报告了冲突,而impl<T, U> WrapOut for HashMap<T, Wrap<U>>没有.

trait WrapOut {
    type Target;
    fn wrap_out(self) -> Self::Target;
}

impl<T, U> WrapOut for HashMap<Wrap<T>, Wrap<U>> 
where T: std::cmp::Eq + std::hash::Hash
{
    type Target = HashMap<T, U>;
    fn wrap_out(self) -> HashMap<T, U> {
        println!("both");
        self.into_iter().map(|(k, v)| (k.val, v.val)).collect()
    }
    
}

impl<T, U> WrapOut for HashMap<T, Wrap<U>> 
where T: std::cmp::Eq + std::hash::Hash
{
    type Target = HashMap<T, U>;
    fn wrap_out(self) -> HashMap<T, U> {
        println!("only val");
        self.into_iter().map(|(k, v)| (k, v.val)).collect()
    }
    
}

// Occurs Error!
impl<T, U> WrapOut for HashMap<Wrap<T>, U> 
where T: std::cmp::Eq + std::hash::Hash
{
    type Target = HashMap<T, U>;
    fn wrap_out(self) -> HashMap<T, U> {
        println!("only key");
        self.into_iter().map(|(k, v)| (k.val, v)).collect()
    }
    
}

错误信息:

   |
51 | impl<T, U> WrapOut for HashMap<Wrap<T>, Wrap<U>> 
   | ------------------------------------------------ first implementation here
...
75 | impl<T, U> WrapOut for HashMap<Wrap<T>, U> 
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `HashMap<Wrap<_>, Wrap<_>>`

Rust Playground

希望您能解释为什么关联的类型版本的代码不能工作,以及背后的原因? 哪种版本的代码更好?

推荐答案

WrapOut个Iml没有冲突的原因有点微妙:

impl<T, U> WrapOut for HashMap<Wrap<T>, Wrap<U>> 
where T: std::cmp::Eq + std::hash::Hash ...

impl<T, U> WrapOut for HashMap<Wrap<T>, U> 
where T: std::cmp::Eq + std::hash::Hash ...

这两个是冲突的,因为Wrap<U>U的子集.U是任何类型,Wrap<U>是一种类型,所以它们是冲突的.如果值为Wrap<U>,Rust不能 Select 一个实现而不是另一个,它们都是有效的.

impl<T, U> WrapOut for HashMap<Wrap<T>, Wrap<U>> 
where T: std::cmp::Eq + std::hash::Hash ...

impl<T, U> WrapOut for HashMap<T, Wrap<U>> 
where T: std::cmp::Eq + std::hash::Hash ...

另一方面,这两者并不冲突,因为T被限制在Eq + Hash上.Wrap<T>没有实现Eq + Hash,所以Rust可以区分这两个隐式.如果你在Wrap#[derive(Eq, Hash)],内幕就会冲突.

然而,目前还不太清楚这与通用版本有什么不同.毕竟,同样的论据也适用于此:

impl<T, U> WrapOut<HashMap<T, U>> for HashMap<Wrap<T>, Wrap<U>> 
where T: std::cmp::Eq + std::hash::Hash ...

impl<T, U> WrapOut<HashMap<T, U>> for HashMap<Wrap<T>, U> 
where T: std::cmp::Eq + std::hash::Hash ...

U是任何类型,Wrap<U>是一种类型,所以它们应该冲突,不是吗?但要想发生冲突,更普遍的IMPL(第二个)必须包括第一个冲突.让我们将TU修复为具体类型AB,并try 创建冲突.当我们为TU插入AB时,第一个毯子Iml会给出以下结果:

impl WrapOut<HashMap<A, B>> for HashMap<A, Wrap<B>>
...

要想发生冲突,我们必须能够从第二个毯子实施中产生同样的影响.要从第二个一揽子IMPL生成匹配的IMPL,我们必须为TU插入AWrap<B>,以使实现类型匹配,但这实际上提供了:

impl WrapOut<HashMap<A, Wrap<B>>> for HashMap<A, Wrap<B>>
//                      ^^^^^^^
...

如果我们让实现类型匹配,我们实现的trait个类型就会改变,所以不可能有冲突.

至于你更一般的问题,带泛型参数的trait和带关联类型的trait之间的区别就是,对于给定的类型,你只能实现一次带关联类型的trait.Rust无法区分这些impls,它们看起来都一样:

impl Trait for Type {
    type A = X;
}
impl Trait for Type {
    type A = Y;
}

但是使用泛型参数,只要泛型类型参数不同,就可以实现任意多次:

impl Trait<X> for Type {}
impl Trait<Y> for Type {}

一旦你做了blanket impls,你就开始不得不处理Rust的coherence rules,这可能会变得非常微妙和复杂,正如你上面看到的.

哪一种更可取完全取决于用例.有些特征应该是泛型的,有些应该使用关联类型.

在这种情况下,使其成为泛型意味着可以将一个类型展开为多个不同的类型,这可能更可取.请注意,这将使类型推断变得更加困难,因此您将需要比使用关联类型更频繁地添加注释.

Rust相关问答推荐

有没有方法处理rust中嵌套的ok_or()?

MPSC频道在接收器处阻塞

如何点击()迭代器?

无符号整数的Rust带符号差

在Rust中声明和定义一个 struct 体有什么区别

Rust函数的返回值不能引用局部变量或临时变量

为什么rustc会自动降级其版本?

如何在Rust中使用Serde创建一个自定义的反序列化器来处理带有内部标记的枚举

如何从 rust 中的同一父目录导入文件

使用 lalrpop 在 rust 中解析由 " 引用的字符串

如何递归传递闭包作为参数?

&self 参数在 trait 的功能中是必需的吗?

返回迭代器的特征

无法理解 Rust 对临时值的不可变和可变引用是如何被删除的

在 Rust 中,为什么整数溢出有时会导致编译错误或运行时错误?

只有一个字符被读入作为词法分析器的输入

当我不满足特征界限时会发生什么?

你能告诉我如何在 Rust 中使用定时器吗?

如果我不想运行析构函数,如何移出具有析构函数的 struct ?

为什么这个 Trait 无效?以及改用什么签名?