为何临时Ref引发`cannot assign to X`错误,已有Ref却正常?
问题代码
用户编写的遍历双向链表的函数无法编译,代码如下:
use std::cell::RefCell; use std::rc::{Rc, Weak}; struct Node { pub data: i32, pub next: Option<Rc<RefCell<Node>>>, pub prev: Option<Weak<RefCell<Node>>>, } fn print_iterate_doesnt_work(node: &Rc<RefCell<Node>>) { let mut current_node = node.clone(); loop { current_node = { let current_node_ref = current_node.borrow(); print!("{} ", current_node_ref.data); if current_node_ref.next.is_none() { return; } current_node.borrow().next.as_ref().unwrap().clone() } } }
编译报错信息
error[E0506]: cannot assign to `current_node` because it is borrowed --> src/lib.rs:13:9 | 13 | current_node = { | ^^^^^^^^^^^^ `current_node` is assigned to here but it was already borrowed ... 20 | current_node.borrow().next.as_ref().unwrap().clone() | --------------------- | | | `current_node` is borrowed here | a temporary with access to the borrow is created here ... 21 | } 22 | } | - ... and the borrow might be used here, when that temporary is dropped and runs the destructor for type `Ref<'_, Node>` | = note: the temporary is part of an expression at the end of a block; consider forcing this temporary to be dropped sooner, before the block's local variables are dropped = note: borrow occurs due to deref coercion to `RefCell<Node>` note: deref defined here --> /playground/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/alloc/src/rc.rs:2124:5 | 2124 | type Target = T; | ^^^^^^^^^^^ help: for example, you could save the expression's value in a new local variable `x` and then make `x` be the expression at the end of the block | 20 | let x = current_node.borrow().next.as_ref().unwrap().clone(); x | +++++++ +++ For more information about this error, try `rustc --explain E0506`.
错误原因解析
1. 临时Ref的生命周期冲突
Rust有明确的临时变量生命周期规则:块末尾表达式的临时变量会被延长到整个块结束时才销毁。原代码最后一行的current_node.borrow()会创建一个临时的Ref<'_, Node>对象,这个对象要等到块内所有局部变量销毁后,才会执行析构函数释放RefCell的借用权限。
而块开头已经通过current_node_ref = current_node.borrow()获取了一次不可变借用,此时current_node处于被借用状态。当我们试图执行current_node = { ... }赋值操作时,临时Ref还未销毁,current_node的借用状态尚未解除,因此触发了cannot assign to current_node because it is borrowed的错误。
2. 析构函数相关错误信息的含义
Ref类型的析构函数负责将RefCell的借用计数减1,释放借用权限。编译器的担忧在于:临时Ref在块结束时才销毁,此时current_node已经被重新赋值,原有的Rc<RefCell<Node>>可能被丢弃,但临时Ref还持有对旧RefCell的引用,存在悬垂引用的风险——虽然实际运行时不会真的出现问题,但Rust的借用检查器是基于静态规则判断,而非运行时分析。
3. 改用current_node_ref.next...可编译的原因
修改后的代码直接使用已获取的current_node_ref(它是current_node.borrow()得到的Ref引用)访问next,不需要再次调用current_node.borrow()创建新的临时Ref。此时:
current_node_ref作为块内局部变量,会在块结束前被销毁,同时释放RefCell的借用权限。- 执行
current_node = ...赋值时,current_node的借用状态已经解除,完全符合Rust的借用规则。
4. “保存到局部变量”建议的原理
将最后一行的结果存入局部变量x后,current_node.borrow()创建的临时Ref会在赋值给x之后立即销毁(因为x是Rc<RefCell<Node>>的克隆,和Ref无关联)。此时块结束前,临时Ref已经释放了借用权限,current_node就可以被安全赋值。
修复后的代码示例
use std::cell::RefCell; use std::rc::{Rc, Weak}; struct Node { pub data: i32, pub next: Option<Rc<RefCell<Node>>>, pub prev: Option<Weak<RefCell<Node>>>, } fn print_iterate_works(node: &Rc<RefCell<Node>>) { let mut current_node = node.clone(); loop { current_node = { let current_node_ref = current_node.borrow(); print!("{} ", current_node_ref.data); match ¤t_node_ref.next { None => return, Some(next_node) => next_node.clone(), } } } }
内容的提问来源于stack exchange,提问作者Евгений Павлов

