Rust如何为自定义递归枚举实现Add trait且不移动原有数据
实现思路
Rust 的运算符 trait 遵循孤儿规则,且值类型的运算符默认走转移语义,结合你的需求(不移动原始x/y、加法返回Sum变体),有两种成熟的实现方案:
方案1:直接为原枚举同时实现值和引用的Add
这种方案不需要修改你原有的枚举定义,只需要补充两个Add实现,平时用&x + &y写法即可避免所有权转移:
代码实现
首先补充必要导入:
use std::rc::Rc; use std::ops::Add;
你原有的枚举定义不变:
#[derive(Debug)] enum Value<T: Add + Copy> { Sum(Rc<Value<T>>, Rc<Value<T>>), Scalar(T) }
先为Value实现Clone(Rc的克隆仅增加引用计数,性能开销极低):
impl<T: Add + Copy> Clone for Value<T> { fn clone(&self) -> Self { match self { Self::Sum(a, b) => Self::Sum(a.clone(), b.clone()), Self::Scalar(v) => Self::Scalar(*v), } } }
核心实现:为&Value<T>实现Add,输入引用不会转移所有权:
impl<T: Add + Copy> Add for &Value<T> { type Output = Value<T>; fn add(self, other: Self) -> Self::Output { Value::Sum(Rc::new(self.clone()), Rc::new(other.clone())) } }
如果需要支持x + y的写法,可以补充值类型的Add实现(注意:该写法会转移x/y的所有权,调用后x/y不可再用,如果要保留请用&x + &y):
impl<T: Add + Copy> Add for Value<T> { type Output = Self; fn add(self, other: Self) -> Self { &self + &other } }
使用示例
let x = Value::Scalar(0.3f32); let y = Value::Scalar(0.3f32); // 用引用相加,x、y所有权不会转移,后续仍可使用 let z = &x + &y; // 此时z为Value::Sum类型,符合预期 println!("{:?}", z);
方案2:外层用自定义类型包装Rc(推荐,写法更简洁)
如果不想每次都写引用符号,可以把枚举变体抽为内部类型,外层用自定义的Value包装Rc,因为外层类型是你自己定义的,就可以直接实现Add,且克隆成本几乎为0:
代码实现
use std::rc::Rc; use std::ops::Add; #[derive(Debug, Clone)] enum ValueInner<T: Add + Copy> { Sum(Rc<ValueInner<T>>, Rc<ValueInner<T>>), Scalar(T) } #[derive(Debug, Clone)] pub struct Value<T: Add + Copy>(Rc<ValueInner<T>>); // 构造Scalar的快捷方法(可选) impl<T: Add + Copy> Value<T> { pub fn new_scalar(v: T) -> Self { Self(Rc::new(ValueInner::Scalar(v))) } } // 实现值类型的Add,内部仅克隆Rc(加引用计数) impl<T: Add + Copy> Add for Value<T> { type Output = Self; fn add(self, other: Self) -> Self::Output { Self(Rc::new(ValueInner::Sum(self.0.clone(), other.0.clone()))) } } // 同时实现引用类型的Add,满足不同场景需求 impl<T: Add + Copy> Add for &Value<T> { type Output = Value<T>; fn add(self, other: Self) -> Self::Output { Value(Rc::new(ValueInner::Sum(self.0.clone(), other.0.clone()))) } }
使用示例
let x = Value::new_scalar(0.3f32); let y = Value::new_scalar(0.3f32); // 写法1:克隆后相加,x、y仍可继续使用,克隆成本极低 let z = x.clone() + y.clone(); // 写法2:用引用相加,无所有权转移 let z = &x + &y;
常见疑问解答
- 为什么不能直接为
Rc<Value<T>>实现Add?
受Rust孤儿规则限制:不允许为外部定义的trait(Add属于标准库)为外部定义的类型(Rc属于标准库)实现,必须用自定义类型包装一层才可以。 - AsRef怎么用?
当前场景下不是必须的,如果你需要写泛型代码统一处理Value和内部枚举的引用,可以为Value实现AsRef:impl<T: Add + Copy> AsRef<ValueInner<T>> for Value<T> { fn as_ref(&self) -> &ValueInner<T> { &self.0 } }
内容的提问来源于stack exchange,提问作者Achim
相关产品推荐
相关产品推荐

