Rust报错‘cannot move out of `*variable`...’原因及循环差异解析
问题场景
你编写了一段计算射击总分的Rust代码:
enum Shot { Bullseye, Hit(f64), Miss, } impl Shot { fn points(self) -> i32 { match self { Shot::Bullseye => 5, Shot::Hit(x) if x < 3.0 => 2, Shot::Hit(x) => 1, _ => 0, } } } fn main() { let mut shots: Vec<Shot> = Vec::new(); shots.push(Shot::Bullseye); shots.push(Shot::Hit(5.0)); shots.push(Shot::Miss); let mut total = 0; for shot in shots.iter() { let points = shot.points(); total += points } println!("Total: {}", total); }
运行后触发所有权相关错误:
error[E0507]: cannot move out of `*shot` which is behind a shared reference | 68 | let points = shot.points(); | ^^^^^-------- | | | | | `*shot` moved due to this method call | move occurs because `*shot` has type `Shot`, which does not implement the `Copy` trait | note: this function takes ownership of the receiver `self`, which moves `*shot` --> src/main.rs:21:15 | 21 | fn points(self) -> i32 { | ^^^^
但将循环改为for shot in shots后即可正常编译,下面解析其中的核心原因。
关键问题解析
1. 为什么调用points()会导致shot被移动?
你定义的points方法接收者是self(而非&self或&mut self),这意味着调用该方法时必须把Shot实例的所有权转移给方法。因为Shot没有实现Copy trait,Rust不会自动复制实例,调用points()就会直接把原实例“移走”,原位置的实例将不再可用。
2. 两种循环写法的本质差异
for shot in shots.iter():iter()返回的迭代器会产生共享引用&Shot,循环变量shot是&Shot类型。当你调用shot.points()时,Rust会自动解引用得到*shot(即Shot实例),但points()需要所有权,这就相当于尝试从共享引用后面把实例移走——而Rust的内存安全规则禁止这种操作:共享引用保证原数据不会被修改或移动,否则其他引用会变成悬空状态,引发内存错误。for shot in shots:直接遍历Vec<Shot>时,迭代器会把Vec中的每个Shot实例转移所有权给循环变量shot。此时shot是Shot类型,调用points()只是把这个局部变量的所有权转移给方法,完全符合规则——注意这种遍历会消耗原Vec,遍历结束后shots就无法再被使用了。
优化方案(可选)
如果不想消耗原Vec的所有权,可以修改points方法的接收者为共享引用,这样无需转移所有权就能计算分数:
impl Shot { fn points(&self) -> i32 { match self { Shot::Bullseye => 5, Shot::Hit(x) if *x < 3.0 => 2, Shot::Hit(_) => 1, _ => 0, } } }
修改后,无论是用shots.iter()还是直接遍历&shots,都能正常调用points(),且原Vec的所有权依然保留。
内容的提问来源于stack exchange,提问作者Carven

