如何在Rust中实现值和引用的惯用运算符重载?

ide*_*n42 9 operator-overloading rust

当实现原始固定大小的矢量类型(float2例如)时,我想支持AddSub特征.后来,我想支持Mul*Assign.

查看文档和其他示例,我想出了这个:

use std::ops::{Add, Sub};

#[derive(Copy, Clone)]
struct float2(f64, f64);

impl Add for float2 {
    type Output = float2;
    fn add(self, _rhs: float2) -> float2 {
        float2(self.0 + _rhs.0, self.1 + _rhs.1)
    }
}

impl Sub for float2 {
    type Output = float2;
    fn sub(self, _rhs: float2) -> float2 {
        float2(self.0 - _rhs.0, self.1 - _rhs.1)
    }
}
Run Code Online (Sandbox Code Playgroud)

这适用于基本的例子,但是我在实践中发现我会经常与作为参数传入,以及局部引用最终float2在栈上的.

混合这些我需要:

  • 取消引用变量(好但是使代码的可读性稍差).
  • 声明运算符也会重载引用的组合.

例:

impl<'a, 'b> Add<&'b float2> for &'a float2 {
    type Output = float2;
    fn add(self, _rhs: &'b float2) -> float2 {
        float2(self.0 + _rhs.0, self.1 + _rhs.1)
    }
}
impl<'a> Add<float2> for &'a float2 {
    type Output = float2;
    fn add(self, _rhs: float2) -> float2 {
        float2(self.0 + _rhs.0, self.1 + _rhs.1)
    }
}
impl<'b> Add<&'b float2> for float2 {
    type Output = float2;
    fn add(self, _rhs: &'b float2) -> float2 {
        float2(self.0 + _rhs.0, self.1 + _rhs.1)
    }
}

/*... and again for Sub */
Run Code Online (Sandbox Code Playgroud)

虽然这允许在不解除引用的情况下编写表达式.枚举每个组合变得相当繁琐,特别是在添加更多操作和类型(float3,float4...)时.

有一种普遍接受的方式......

  • 自动强制运算符重载类型?
  • 使用语言或语言的其他一些功能,以避免繁琐的重复?

或者预计开发人员要么:

  • 根据需要显式访问变量作为引用.
  • 根据需要显式取消引用变量.
  • 编写了大量重复的运算符重载函数.

注意,我现在是初学者,我已经在Rust中检查了一些非常高级的数学库,它们在我脑海中,而我可以使用它们 - 我想了解如何为我自己的类型编写运算符重载.

She*_*ter 6

Rust的好处在于它是开源的.这意味着您可以看到该语言的作者如何解决问题.最接近的模拟是原始整数类型:

macro_rules! add_impl {
    ($($t:ty)*) => ($(
        #[stable(feature = "rust1", since = "1.0.0")]
        impl Add for $t {
            type Output = $t;

            #[inline]
            fn add(self, other: $t) -> $t { self + other }
        }

        forward_ref_binop! { impl Add, add for $t, $t }
    )*)
}
Run Code Online (Sandbox Code Playgroud)

forward_ref_binop定义为:

macro_rules! forward_ref_binop {
    (impl $imp:ident, $method:ident for $t:ty, $u:ty) => {
        #[stable(feature = "rust1", since = "1.0.0")]
        impl<'a> $imp<$u> for &'a $t {
            type Output = <$t as $imp<$u>>::Output;

            #[inline]
            fn $method(self, other: $u) -> <$t as $imp<$u>>::Output {
                $imp::$method(*self, other)
            }
        }

        #[stable(feature = "rust1", since = "1.0.0")]
        impl<'a> $imp<&'a $u> for $t {
            type Output = <$t as $imp<$u>>::Output;

            #[inline]
            fn $method(self, other: &'a $u) -> <$t as $imp<$u>>::Output {
                $imp::$method(self, *other)
            }
        }

        #[stable(feature = "rust1", since = "1.0.0")]
        impl<'a, 'b> $imp<&'a $u> for &'b $t {
            type Output = <$t as $imp<$u>>::Output;

            #[inline]
            fn $method(self, other: &'a $u) -> <$t as $imp<$u>>::Output {
                $imp::$method(*self, *other)
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

编写引用的特征的包装器实现对于简单地取消引用并调用面向值的版本当然是有效的.

  • 包“forward_ref”提供了“forward_ref_binop”、“forward_ref_op_assign”和“forward_ref_unop”宏,尽管它们与核心中的宏相比有所简化。 (2认同)