本节摘要:讲清引用即"不转移产权的访问凭证",借用铁律为"一个可变引用或任意多个不可变引用";用 E0502、E0384 系、E0507 三类高频案件演示铁律如何执行,并给出常见的合规改法。读完你能在编译前预判一段代码的借用是否合法。
不动产权也能访问:&x 旁听(只读),&mut x 出庭发言(可写)。两条铁律:
第一条本质是"读写互斥、写写互斥、读读共享"。数据竞争在语法层面失去生存空间——三条卷宗里的第三宗在此了结。
案件一:读写并存(E0502)。
let mut case = String::from("待审"); let r = &case; let w = &mut case; // E0502: cannot borrow as mutable, // because it is also borrowed as immutable
改法一:先用完只读引用,其作用域结束(最后使用点)后再取可变——Rust 的非词法生命周期允许"用完即还";改法二:直接复制所需数据,断开耦合。
案件二:还了再借(作用域重排后合法)。
let mut case = String::from("待审"); let len = { let r = &case; r.len() }; // 只读引用在此出清 case.push_str(",已审"); // 合法:无并存
案件三:可变引用期间冻结原变量。
let mut v = vec![1, 2, 3]; let w = &mut v; v.push(4); // 驳回:w 在身,产权人被冻结 w.push(5); // 合法

fn dossier() -> &String { let s = String::from("临时卷宗"); &s // E0597:s 在函数结束释放,引用悬垂 }
函数想把数据交出去,要么交所有权(返回 String),要么参数里本来就有外部的引用可用——这就是下一节生命周期的立案理由。
& 只读、&mut 可写,权限写在类型里由编译器执勤;⚠️ 常见坑:与借用检查器僵持时,先别急着 clone——多数情况是"引用活得比需要的久",缩小引用的作用域比复制数据便宜得多。
E0502、E0499、E0506 是借用法庭的三张高频通缉令,同一份现场材料放在一起审,差异一目了然。
fn main() { let mut ledger = String::from("账目"); // 案一 E0502:不可变借用存活期间发起可变借用 let view = &ledger; // ledger.push_str("追加"); // ^^^ first borrow later used here println!("读取 {}", view); // 案二 E0499:可变借用出现两次 let w1 = &mut ledger; let w2 = &mut ledger; w1.push('a'); w2.push('b'); // 案三 E0506:把值移出借用 let r = &ledger; // let owned = *r; // cannot move out of `*r` let copied = r.clone(); // 合规:复制新产权 }
案一与案二的共同法理是"别名与修改不可共存":要么多个读者,要么一个写者。案三则提醒引用只是旁听席,不持有产权,想把证物带走必须 clone。注意案二能通过与否取决于 NLL:若 w1 的最后一次使用在 w2 诞生之前,两次可变借用并不重叠,合法——判的是控制流上的区间,不是文本顺序。
E0502 的修法几乎总是一样的:把读取的结果提前物化,结束读借用,再进入写。
fn main() { let mut ledger = String::from("账目"); let snapshot: String = (&ledger).clone(); // 读借用当场结束 ledger.push_str("追加"); // 写借用独占 println!("旧 {} 新 {}", snapshot, ledger); }
另一常见现场是"先查表再改表":if map.contains_key(k) { map.insert(...) } 会因两次借用重叠被驳回,标准库为此准备了 entry 一次拿写权限的 API,第 4 章集合一节会作为判例展开。修法的总原则:让每个借用的存活区间尽量短,短到肉眼可见,冲突自然消失。
切片 &[T] 与 &str 是"借用一整族值"的标准形态,也是函数签名的首选——它把"我要读一段连续数据"表达得最宽松,调用方传数组、Vec、字符串都行。
fn total(pts: &[u32]) -> u32 { pts.iter().sum() } fn main() { let arr = [1u32, 2, 3]; let v = vec![4u32, 5, 6]; println!("{} {} {}", total(&arr), total(&v), total(&v[1..])); }
签名写 &Vec<u32> 的初版实现功能相同,却把调用方锁死在 Vec 一种容器上——借用切片是解除不必要限制的通用改判。心里把 &Vec<T> 读作"借了整个保管室",把 &[T] 读作"借了证物的一段视图",选择就不难做。
| 现场 | 速裁修法 |
|---|---|
| 循环里改集合 | 先收集待改项再统一处理 |
| 读完再写 | 克隆快照或提前结束读借用 |
| 方法调用报借用冲突 | 让调用产出值而非持有借用 |
| 闭包捕获与借用重叠 | clone 进闭包或重组数据 |
借用报错的修复方向只有三个家族:缩短区间、改克隆、重组数据。反复 clone 无果时,几乎总是第三家族的问题——数据结构把"读"与"写"的职责糅在了一个所有者身上,拆开(读写分离的字段、或引入中间结构)比继续对抗编译器省力得多。