外观
雷区 7:E0505 从借用中把值搬走
报错码:E0505
症状
rust
struct Foo {
data: String,
}
fn main() {
let mut foo = Foo {
data: String::from("hello"),
};
let borrow = &foo.data;
let moved = foo.data;
println!("{} {}", borrow, moved);
}编译输出
text
error[E0505]: cannot move out of `foo.data` because it is borrowed
--> src/main.rs:10:17
|
6 | let mut foo = Foo {
| ------- binding `foo` declared here
...
9 | let borrow = &foo.data;
| --------- borrow of `foo.data` occurs here
10| let moved = foo.data;
| ^^^^^^^^ move out of `foo.data` occurs here
11| println!("{} {}", borrow, moved);
| ------ borrow later used here诊断
这个雷区是"移动"和"借用"同台打架:
- 第 9 行
&foo.data→ 借了foo.data来看(借条在borrow手里) - 第 10 行
foo.data→ 想把foo.data搬走(移动给moved) - 冲突!借条还指着
foo.data,东西却要被搬走——搬走之后,borrow手里的借条指向哪儿?指向空气
"借出去的东西不能搬走":别人正看着呢,你把东西端走了,看的人怎么办?
真实项目的高频姿势:想从结构体里"取出"一个字段(移动),但这个结构体(或其字段)正被借用着——比如"读一下配置,然后要把配置的某部分拿出来"。
解药
解药一:复印一份(clone),原封不动——借条和移动都保住:
rust
fn main() {
let mut foo = Foo {
data: String::from("hello"),
};
let borrow = foo.data.clone();
let moved = foo.data;
println!("{} {}", borrow, moved);
}解药二:先搬走,再借用——调整顺序,借条自然失效:
rust
fn main() {
let mut foo = Foo {
data: String::from("hello"),
};
let moved = foo.data; // 先搬走
let borrow = &moved; // 再借,借的是新主人
println!("{} {}", borrow, moved);
}解药三:借用结构体整体,而不是字段——&mut foo 借整个,字段随便动:
rust
fn main() {
let mut foo = Foo {
data: String::from("hello"),
};
let data = &mut foo.data; // 借整个结构体的可变引用
data.push_str(" world");
println!("{}", foo.data);
}解药三的细节
&mut foo.data 借的是字段(借用一部分),和 &mut foo(借整个)不同。部分借用让编译器更精确——只锁定被借的字段,其他字段照常能用。
预防
- E0505 和 E0502 是兄弟:一个"边借边改",一个"边借边搬"。看到时间线里"borrow occurs here"和"move occurs here"同时出现,思路一样:让借用先下班,或者别搬
- "从结构体里取字段"这个操作,先想:取出来之后,结构体还要不要用?还要用 → clone;不用了 → 整个结构体移动(
let foo = foo;拿走全部) Option::take是"搬字段"的专业工具:想从结构体里"掏"出一个字段,同时结构体还能活着——把字段改成Option,用.take()(第 2 章练习会碰到的神器,错误处理篇还会见)