rust - 为移动和借用的所有组合实现 Add、Sub、Mul、Div

标签 rust

我有一个应该实现基本数学运算符的简单结构。最初我不想让这些消耗操作数,所以我实现了引用的特征,例如 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);

所以我最终实现了移动和借用的其他排列,它们只是服从第一个排列:

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等等?

更新:

@EvilTak 在评论中提出了一个建议,通过实现 B: Borrow<MyType> 来减少样板组合。 MyType 上的版本和 &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
    }
}

最佳答案

进入这个兔子洞后,我将回答我自己的问题。

我开始使用 Borrow 来减少我需要实现的函数数量:

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在一起。

尝试使用 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 both Borrow<MyType>Borrow<MyOtherType>同时,编译器不知道要使用哪个实现。

那时我决定改为尝试宏路线。如您所料,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 的类型相同,如果您正在执行矩阵乘以向量等操作,情况可能并非如此。

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

我现在已经使用了这个库。如果情况发生变化,我会尝试在此处进行更新。

关于rust - 为移动和借用的所有组合实现 Add、Sub、Mul、Div,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/74363718/

相关文章:

rust - Rust 中有 POD 类型的概念吗?

generics - 如何为 Rust 泛型函数中绑定(bind)的大小多类型赋予可重用名称

rust - 为什么胖指针有时会向外渗透?

vector - 增加 Vec<usize> 的最后一个元素

rust - 在 Rust 中统一分配是什么意思?

rust - rust 双引用值

reference - 迭代器通过引用返回项目,生命周期问题

rust - 如何摆脱错误 "struct field shorthands are unstable"?

enums - 类似结构的枚举可以用作类型吗?

rust - 什么可能导致难以重现的 Hyper HTTP 响应截断?