我有一个简单的 struct ,应该实现基本的数学运算符.最初,我不希望这些操作数消耗操作数,所以我实现了引用的特征,例如Add:

impl<'a, 'b> Add<&'b MyType> for &'a MyType {
    type Output = MyType;

    fn add(self, rhs: &'b MyType) -> Self::Output {
        // Implementation...
    }
}

这允许我执行以下操作:

let result = &v1 + &v2;

其中v1v2是类型MyType.

然后我意识到,有时使用操作数在语法上更方便,例如在执行以下操作时:

let result = &v1 + &v2 + &v3;

因为有一个中间结果,所以上面的结果不会编译,您必须这样做:

let result = &v1 + &(&v2 + &v3);

所以我最终实现了Move和Borry的其他排列,它们只是遵循第一个排列:

impl<'a> Add<&'a MyType> for MyType {
    type Output = MyType;

    fn add(self, rhs: &'a MyType) -> Self::Output {
        &self + rhs
    }
}

impl<'a> Add<MyType> for &'a MyType {
    type Output = MyType;

    fn add(self, rhs: MyType) -> Self::Output {
        self + &rhs
    }
}

impl Add<MyType> for MyType {
    type Output = MyType;

    fn add(self, rhs: MyType) -> Self::Output {
        &self + &rhs
    }
}

这很管用,但很麻烦.

我寻找了一种更简单的方法,比如使用Borrow<T>:

impl<B> Add<B> for B
where
    B: Borrow<MyType>,
{
    type Output = MyType;

    fn add(self, rhs: B) -> Self::Output {
        // Implementation...
    }
}

但可以理解的是,由于type parameter B must be used as the type parameter for some local type,这不会编译.

有没有其他技巧可以避免所有这些针对Add/Sub/Mul/Div等的样板实现?

Update:

@EvilTak在 comments 中提出了一个建议,通过在MyType&MyType上明确实现B: Borrow<MyType>版本来减少样板组合.这是一个很好的改进:

impl<'a, B> Add<B> for &'a MyType
where
    B: Borrow<MyType>,
{
    type Output = MyType;

    fn add(self, rhs: B) -> Self::Output {
        // Implementation...
    }
}

impl<B> Add<B> for MyType
where
    B: Borrow<MyType>,
{
    type Output = MyType;

    fn add(self, rhs: B) -> Self::Output {
        &self + rhs
    }
}

推荐答案

在经历了这个兔子洞之后,我将回答我自己的问题.

我开始使用Borry来减少我需要实现的功能的数量:

impl<'a, B> Add<B> for &'a MyType
where
    B: Borrow<MyType>,
{
    type Output = MyType;

    fn add(self, rhs: B) -> Self::Output {
        // Implementation...
    }
}

impl<B> Add<B> for MyType
where
    B: Borrow<MyType>,
{
    type Output = MyType;

    fn add(self, rhs: B) -> Self::Output {
        &self + rhs
    }
}

这个方法运行得很好,直到我还需要把MyTypeMyOtherType加在一起.

try 使用Borrow<T>实现该功能时出现错误conflicting implementations of trait:

impl<'a, B> Mul<B> for &'a MyType
where
    B: Borrow<MyOtherType>,
{
    type Output = MyOtherType;

    fn mul(self, rhs: B) -> Self::Output {
        /// Implementation...
    }
}

这是因为类型could theoretically implement bothBorrow<MyType>Borrow<MyOtherType>同时存在,而编译器不知道要使用哪种实现.

在这一点上,我决定try 宏观路由.如你所料,this has been done before by others美元.

几个不同的地方建议使用impl_ops,后来被auto_ops取代.

该 crate 允许您通过执行以下操作来定义运算符的所有各种组合:

impl_op_ex!(+ |a: &DonkeyKong, b: &DonkeyKong| -> DonkeyKong { DonkeyKong::new(a.bananas + b.bananas) });

但是,此机箱具有不能使用泛型的限制.在我的例子中,MyType实际上是Matrix<const M: usize, const N: usize>,所以我需要泛型支持.

然后我遇到了auto_impl_ops,它允许您从单个AddAsign特征实现生成所有不同的添加组合(对于其他操作也是如此),并且还支持泛型.

use std::ops::*;
# 
# #[derive(Clone, Default)]
# struct A<T>(T);

#[auto_impl_ops::auto_ops]
impl<M> AddAssign<&A<M>> for A<M>
where
    for<'x> &'x M: Add<Output = M>,
{
    fn add_assign(&mut self, other: &Self) {
        self.0 = &self.0 + &other.0;
    }
}

这里的一个限制是结果必须始终是与Self相同的类型,例如,如果您正在进行矩阵乘以向量,则情况可能不是这样.

另一个可能的问题是,对于二元运算符,机箱将在使用赋值运算符实现之前克隆左侧的值,并返回克隆.对于矩阵乘法,我还必须在MulAssign实现中克隆self,否则我将覆盖仍然用于矩阵乘法的数据.这意味着这里至少有一个冗余的内存副本,如果我手动实现操作符,就不会有这个副本.

我现在已经 Select 了这个图书馆.如果情况有变,我会试着在这里更新.

Rust相关问答推荐

计算具有相邻调换且没有插入或删除的序列的距离

如何在不安全的代码中初始化枚举 struct

当rust中不存在文件或目录时,std::FS::File::Create().unwire()会抛出错误

如何为rust trait边界指定多种可能性

为什么Option类型try块需要类型注释?

如何初始化选项<;T>;数组Rust 了?

为什么我必须使用 PhantomData?在这种情况下它在做什么?

从管道读取后重置标准输入

为什么实现特征的对象期望比具体对象有更长的生命周期?

`移动||异步移动{...}`,如何知道哪个移动正在移动哪个?

rust 中不同类型的工厂函数

Rust 中函数的类型同义词

预期的整数,找到 `&{integer}`

如何解析 Rust 中的 yaml 条件字段?

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

在 FFI 的上下文中,未初始化是什么意思?

当用作函数参数时,不强制执行与绑定的关联类型

Rust:为什么在 struct 中borrow 引用会borrow 整个 struct?

加入动态数量的期货

将 reqwest bytes_stream 复制到 tokio 文件中