3.2 借用与引用:出庭与旁听


3.2 借用与引用:出庭与旁听

本节摘要:讲清引用即"不转移产权的访问凭证",借用铁律为"一个可变引用或任意多个不可变引用";用 E0502、E0384 系、E0507 三类高频案件演示铁律如何执行,并给出常见的合规改法。读完你能在编译前预判一段代码的借用是否合法。

借用铁律

不动产权也能访问:&x 旁听(只读),&mut x 出庭发言(可写)。两条铁律:

  1. 同一时刻,要么一个可变引用,要么任意多个不可变引用——不可兼得;
  2. 引用必须始终有效(不可悬垂,E0597 在下一节展开)。

第一条本质是"读写互斥、写写互斥、读读共享"。数据竞争在语法层面失去生存空间——三条卷宗里的第三宗在此了结。

三类高频案件

案件一:读写并存(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 章集合一节会作为判例展开。修法的总原则:让每个借用的存活区间尽量短,短到肉眼可见,冲突自然消失。

切片:借用 courtroom 的量具

切片 &[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 无果时,几乎总是第三家族的问题——数据结构把"读"与"写"的职责糅在了一个所有者身上,拆开(读写分离的字段、或引入中间结构)比继续对抗编译器省力得多。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U