如何在我不拥有的类型上实现我不拥有的特质?

85

我想为Vec实现Shl特质,下面是代码。这将使得像vec << 4这样的操作成为可能,这对于vec.push(4)来说是不错的语法糖。

use std::ops::Shl;

impl<T> Shl<T> for Vec<T> {
    type Output = Vec<T>;

    fn shl(&self, elem: &T) -> Vec<T> {
        self.push(*elem);
        *self
    }
}

fn main() {
    let v = vec![1, 2, 3];
    v << 4;
}

编译出现以下错误:
无法在此包中未定义特质和类型的情况下提供扩展实现[E0117]
或者
类型参数T必须用作某些本地类型(例如MyStruct )的类型参数;只有在当前包中定义的特质才能为类型参数实现[E0210]
据我所知,我必须打补丁到stdlib,更具体地说是collections :: vec包。是否有其他方法可以更改此代码以成功编译?

10
这是出于设计而不可能的。 - A.B.
3个回答

106

虽然您无法直接这样做,但通常的解决方法是只需将您想要的类型包装在自己的类型中,并在该类型上实现trait。

use somecrate::FooType;
use somecrate::BarTrait;

struct MyType(FooType);

impl BarTrait for MyType {
    fn bar(&self) {
        // use `self.0` here
    }
}

26
附录:实现 Deref trait 以避免每次输入 <MyType>.0.foo(意思是建议实现 Deref trait,这样就可以通过解引用操作符 * 来代替每次输入 <MyType>.0.foo - Dev Aggarwal
3
这种方法在2020年仍然有效吗? - max
4
@max 是的。这个“限制”是使Rust成为一种好语言的核心因素之一。因此,它在2020年仍然有效,并且在Rust 1版本中可能永远有效。 - S.R
这种使用结构体的方法是否比使用命名字段更好? - Christian Bongiorno
3
@ChristianBongiorno 我认为元组结构更加简洁,更明显地表明它意图成为一个新类型,而不是具有命名字段的情况。 - Jeremy Meadows
请参阅https://stackoverflow.com/q/45086595,了解新类型是否应该实现`Deref` - undefined

32
这将使vec << 4等类似的操作成为可能,这对于vec.push(4)来说是非常方便的语法糖。
虽然这是可以实现的,但通常不建议使用具有意外语义的运算符重载。这引起了批评
以下是一个示例:
use std::ops::Shl;

struct BadVec<T>(Vec<T>);

impl<T> Shl<T> for BadVec<T> {
    type Output = BadVec<T>;

    fn shl(mut self, elem: T) -> Self::Output {
        self.0.push(elem);
        self
    }
}

fn main() {
    let mut v = BadVec(vec![1, 2, 3]);
    v = v << 4;
    assert_eq!(vec![1, 2, 3, 4], v.0)
}

如果你实现了 Deref (DerefMut):
use std::ops::{Deref, DerefMut};

impl<T> Deref for BadVec<T> {
    type Target = Vec<T>;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl<T> DerefMut for BadVec<T> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.0
    }
}

您可以调用 Vec 方法:
fn main() {
    let mut v = BadVec(vec![1, 2, 3]);
    v = v << 4;
    v.truncate(2);
    assert_eq!(2, v.len());
}

请查看newtype_derive装箱枚举派生库,它可以为你生成一些样板代码。

2
官方文档中有一条信息提示:“为智能指针实现Deref使得访问其背后的数据变得方便,这也是它们实现Deref的原因。另一方面,关于DerefDerefMut的规则是专门设计用来适应智能指针的。因此,只应该为智能指针实现Deref以避免混淆。” https://doc.rust-lang.org/stable/std/ops/trait.Deref.html - Tim Abell

7

这是有意禁止的,被称为孤儿规则。它是为了保证特质的一致性。根据孤儿规则,你不能为一个结构体提供特质实现,除非你是定义该结构体的创建者或定义该特质的创建者。如果你能够达到你所要求的,我们将会出现冲突的实现。

不过,有一些变通方法,最常见的是新类型模式。在新类型模式中,我们在本地结构体中包装外部类型,并通过这个包装器来实现所需的方法。

// External struct
use foo::Foo;

// Create a new type.
pub struct Bar(Foo);

// Provide your own implementations
impl Bar {
    pub fn new() -> Self {
        //..
    }
}

了解更多信息,请查看以下参考资料:


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接