当Copy trait不可用时,如何从重载运算符归还结构体所有权?
问题描述
当结构体包含String(因此无法实现Copy trait)时,重载运算符后结构体变量会被移动,无法再次使用。虽然运算符里未修改字符串,但不知道如何实现自定义Copy trait,或通过其他方法解决——希望运算符返回结构体所有权后,原变量还能继续使用。
最小示例代码
use std::ops; struct Value { val: i32, name: String } impl std::fmt::Display for Value { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "{}({})", self.name, self.val) } } impl ops::Add<Value> for Value { type Output = Value; fn add(self, _rhs: Value) -> Value { Value { val: self.val + _rhs.val, name: format!("({}+{})", self.name, _rhs.name) } } } fn main() { let a = Value {val: 5, name: "a".to_string()}; let b = Value {val: 6, name: "b".to_string()}; let mut c = a + b; c = c + a; println!("{}", c); }
编译错误信息
error[E0382]: use of moved value: `a` --> src/main.rs:29:13 | 24 | let a = Value {val: 5, name: "a".to_string()}; | - move occurs because `a` has type `Value`, which does not implement the `Copy` trait ... 27 | let mut c = a + b; | ----- `a` moved due to usage in operator 28 | 29 | c = c + a; | ^ value used here after move | note: calling this operator moves the left-hand side
解决方案
1. 实现针对引用的Add trait
既然不需要获取操作数的所有权,可让运算符接收引用,避免原变量被移动。需要修改Add的实现,支持引用类型的操作数:
use std::ops; struct Value { val: i32, name: String } impl std::fmt::Display for Value { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "{}({})", self.name, self.val) } } // 基础版本:&Value + &Value impl ops::Add<&Value> for &Value { type Output = Value; fn add(self, rhs: &Value) -> Value { Value { val: self.val + rhs.val, name: format!("({}+{})", self.name, rhs.name) } } } // 支持混合使用:&Value + Value impl ops::Add<Value> for &Value { type Output = Value; fn add(self, rhs: Value) -> Value { Value { val: self.val + rhs.val, name: format!("({}+{})", self.name, rhs.name) } } } // 支持混合使用:Value + &Value impl ops::Add<&Value> for Value { type Output = Value; fn add(self, rhs: &Value) -> Value { Value { val: self.val + rhs.val, name: format!("({}+{})", self.name, rhs.name) } } } fn main() { let a = Value {val: 5, name: "a".to_string()}; let b = Value {val: 6, name: "b".to_string()}; let mut c = &a + &b; // 使用引用相加,原变量不会被移动 c = &c + &a; // 重复使用a println!("{}", c); }
2. 为结构体实现Clone trait
如果要保留按值相加的语义,可以给Value派生Clone trait,在需要重复使用的地方调用clone()复制变量:
use std::ops; #[derive(Clone)] // 自动派生Clone,String本身已实现Clone struct Value { val: i32, name: String } impl std::fmt::Display for Value { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "{}({})", self.name, self.val) } } impl ops::Add<Value> for Value { type Output = Value; fn add(self, rhs: Value) -> Value { Value { val: self.val + rhs.val, name: format!("({}+{})", self.name, rhs.name) } } } fn main() { let a = Value {val: 5, name: "a".to_string()}; let b = Value {val: 6, name: "b".to_string()}; let mut c = a.clone() + b; // 克隆a,原变量不会被移动 c = c + a; // 重复使用a println!("{}", c); }
关于Copy trait的说明
包含String的结构体永远无法实现Copy trait。因为Copy要求类型可按位复制,而String内部持有堆内存指针,按位复制会导致两个String指向同一块内存,析构时重复释放内存,引发安全问题——这是Rust内存安全机制的强制要求。
内容的提问来源于stack exchange,提问作者ndsrib
相关产品推荐
相关产品推荐

