Rust中解引用借用与直接借用的差异及借用规则问询
环境: rustc 1.82.0 (f6e511eec 2024-10-15)
问题描述
你对借用场景下*&T与T的区别存在困惑:以下代码中let b = &mut *a可正常编译,但let b = &mut x编译失败;同时取消最后一行注释会触发cannot assign to *a because it is borrowed错误,希望理解背后的Rust借用规则。
fn annoying_borrow() { let mut x = Box::new(1); let a = &mut x; let b = &mut *a; // let b = &mut x; // error: cannot borrow `x` as mutable more than once at a time *a = Box::new(2); // *b = Box::new(3); // error: `*a` is assigned to here but it was already borrowed }
核心差异与规则解析
1. 为什么&mut *a合法,&mut x不合法?
这本质是直接借用和**派生借用(Reborrow)**的区别:
- 当创建
let a = &mut x时,a作为x的可变引用,已经独占了对x的访问权——根据可变引用的独占规则,此时任何直接访问x的行为(比如&mut x)都会违反“同一时间只能有一个可变引用”的核心规则,因此编译报错。 - 而
&mut *a属于从已有可变引用派生新的可变引用(即Reborrow):编译器会识别出b是从a派生的,属于同一借用链,并非直接绕过a访问x。这种派生借用不会打破a的独占性,因为编译器会跟踪b的生命周期,确保它与a的后续使用不冲突。
2. 为什么*a = Box::new(2)合法,但取消*b = ...注释会报错?
这得益于Rust的**非 lexical 生命周期(NLL)**特性:
- 在当前代码中,
b创建后没有被实际使用(注释掉了*b的赋值),编译器会自动推断b的生命周期在*a = Box::new(2)之前就已结束,因此此时a的独占权已恢复,可以正常修改*a。 - 若取消
*b = Box::new(3)的注释,b的生命周期会延续到该行,此时a仍被b借用,根据可变引用的独占规则,不能同时存在活跃的可变引用和对原变量的修改操作,因此编译报错。
3. *&T与T的本质区别
*&T并非直接获取T的原始值,而是通过对引用的解引用,生成一个临时的借用视图。编译器会将其视为从原引用派生的新引用,而非直接访问原始变量。这种派生借用受限于原引用的生命周期,不会打破原引用的独占性或共享性;而直接使用T则是访问原始变量,会触发针对原始变量的借用检查。
借用规则的统一解释
Rust借用规则的核心从未改变,编译器的进化(如NLL)只是让规则的应用更灵活,而非修改核心逻辑:
- 独占性:同一时间,一个值要么只能有一个可变引用,要么可以有任意多个不可变引用,二者不可兼得。
- 有效性:引用必须始终指向有效的值,不能出现悬垂引用。
- 借用链兼容性:从已有引用派生的新引用(Reborrow)属于同一借用链,编译器会跟踪其生命周期,确保不会与原引用的使用冲突。
你在《Programming Rust(第二版)》中看到的描述,正是对上述核心规则的细化:
共享访问即只读访问
被共享引用借用的值是只读的。在共享引用的生命周期内,其指向的对象及该对象可访问的任何内容都无法被修改。该结构中不存在活跃的可变引用,其所有者也被视为只读,整个结构处于冻结状态。
可变访问即独占访问
被可变引用借用的值只能通过该引用访问。在可变引用的生命周期内,不存在其他可访问其指向对象或该对象可访问内容的路径。只有从该可变引用自身借用的引用,其生命周期才能与该可变引用重叠。
这些描述完全契合核心规则:共享引用的只读性对应“多个不可变引用共存”,可变引用的独占性对应“同一时间仅一个可变引用”,而派生引用的允许则是对“借用链兼容性”的说明。
内容的提问来源于stack exchange,提问作者galaxyzen

