Rust结构体操作数为值或引用时,如何实现操作符重载?
我是Rust新手,正尝试重载Mul、Sub等操作符,使自定义Tuple结构体的简单算术函数能够编译。现有代码如下:
#[derive(Debug)] struct Tuple { x: f64, y: f64, z: f64, w: f64, } fn dot(a: &Tuple, b: &Tuple) -> f64 { (a.x * b.x) + (a.y * b.y) + (a.z * b.z) + (a.w * b.w) } impl std::ops::Mul<f64> for Tuple { type Output = Tuple; fn mul(self, rhs: f64) -> Tuple { Tuple{x: self.x * rhs, y: self.y * rhs, z: self.z * rhs, w: self.w * rhs} } } impl std::ops::Mul<f64> for &Tuple { type Output = Tuple; fn mul(self, rhs: f64) -> Tuple { Tuple{x: self.x * rhs, y: self.y * rhs, z: self.z * rhs, w: self.w * rhs} } } impl std::ops::Sub<&Tuple> for Tuple { type Output = Tuple; fn sub(self, rhs: &Tuple) -> Tuple { Tuple{x: self.x - rhs.x, y: self.y - rhs.y, z: self.z - rhs.z, w: self.w - rhs.w} } } impl std::ops::Sub<&Tuple> for &Tuple { type Output = Tuple; fn sub(self, rhs: &Tuple) -> Tuple { Tuple{x: self.x - rhs.x, y: self.y - rhs.y, z: self.z - rhs.z, w: self.w - rhs.w} } } fn reflect(inc: &Tuple, normal: &Tuple) -> Tuple { //inc - normal * 2.0 * inc.dot(&normal) let a = dot(&inc, &normal); let b = 2.0 * a; let c = normal * b; inc - c // <--- compile error: expected `&Tuple`, found struct `Tuple` } #[cfg(test)] mod tests { use super::*; #[test] fn something() { let v = Tuple { x: 1.0, y: -1.0, z: 0.0, w: 0.0 }; let n = Tuple { x: 0.0, y: 1.0, z: 0.0, w: 0.0 }; //let r = reflect(&v, &n); let r = v - n * 2.0 * dot(&v, &n); // <-- compile error: expected `&Tuple`, found struct `Tuple` // ~~~~~~~~~~~~~~~~~~~~~ println!("{:?}", r); } }
编译错误信息:
error[E0308]: mismatched types --> lib/tuple.rs:70:11 | 70 | inc - c | ^ | | | expected `&Tuple`, found struct `Tuple` | help: consider borrowing here: `&c`
我搞不懂需要定义的各种实现,不知道是不是必须为A @ B、A @ &B、&A @ B、&A @ &B每种组合分别实现操作符,同时疑惑不可变引用能不能自动解引用为值,从而复用值版本的操作符。之前我在newtype上用过derive_more::{Mul},但它只生成值对值的二元操作符函数。
问题根源
你当前的Sub实现仅支持**左侧为Tuple/&Tuple、右侧为&Tuple**的场景,但inc - c中inc是&Tuple,c是Tuple值,不符合已实现的方法签名,因此触发类型不匹配错误。
Rust不会自动将值转为引用,也不会自动将不可变引用解引用为值(这涉及所有权转移,编译器不会擅自做主),所以确实需要覆盖操作数的四种组合,但可以通过复用核心逻辑减少重复代码。
解决步骤
1. 为Tuple添加Copy/Clone特性
因为Tuple的字段都是f64(已实现Copy),直接派生Copy和Clone,这样引用可以安全地解引用为值,无需手动克隆:
#[derive(Debug, Copy, Clone)] struct Tuple { x: f64, y: f64, z: f64, w: f64, }
2. 优先实现值对值的核心操作
先写值版本的操作符实现,作为核心逻辑,其他版本都基于它扩展:
// 值对f64的Mul(核心) impl std::ops::Mul<f64> for Tuple { type Output = Tuple; fn mul(self, rhs: f64) -> Tuple { Tuple { x: self.x * rhs, y: self.y * rhs, z: self.z * rhs, w: self.w * rhs, } } } // 值对值的Sub(核心) impl std::ops::Sub for Tuple { type Output = Tuple; fn sub(self, rhs: Tuple) -> Tuple { Tuple { x: self.x - rhs.x, y: self.y - rhs.y, z: self.z - rhs.z, w: self.w - rhs.w, } } }
3. 复用核心实现,快速覆盖其他组合
基于值版本的实现,轻松完成剩余三种组合的操作符:
// 引用对f64的Mul:解引用self调用核心实现 impl std::ops::Mul<f64> for &Tuple { type Output = Tuple; fn mul(self, rhs: f64) -> Tuple { (*self).mul(rhs) } } // 值对引用的Sub:解引用rhs调用核心Sub impl std::ops::Sub<&Tuple> for Tuple { type Output = Tuple; fn sub(self, rhs: &Tuple) -> Tuple { self - *rhs } } // 引用对值的Sub:解引用self调用核心Sub impl std::ops::Sub<Tuple> for &Tuple { type Output = Tuple; fn sub(self, rhs: Tuple) -> Tuple { (*self) - rhs } } // 引用对引用的Sub:解引用两边调用核心Sub impl std::ops::Sub<&Tuple> for &Tuple { type Output = Tuple; fn sub(self, rhs: &Tuple) -> Tuple { (*self) - (*rhs) } }
现在reflect函数里的inc - c和测试代码中的v - n * 2.0 * dot(&v, &n)都能正常编译运行。
4. 用derive_more简化代码
derive_more支持通过属性生成引用相关的实现,不用手动写重复代码:
use derive_more::{Mul, Sub}; #[derive(Debug, Copy, Clone, Mul, Sub)] #[mul(ref)] // 生成引用对f64的Mul #[sub(ref_all)] // 生成所有引用组合的Sub struct Tuple { x: f64, y: f64, z: f64, w: f64, }
#[sub(ref_all)]会自动生成值-引用、引用-值、引用-引用三种Sub实现,#[mul(ref)]生成引用对f64的Mul实现,大幅减少手写代码量。
内容的提问来源于stack exchange,提问作者davidA

