为何在表达式中借用RefCell时,借用生命周期超出预期(但将借用赋值给变量却正常工作)
嘿,我太懂你遇到这个问题时的懵圈感了!RefCell的借用规则有时候确实会搞出这种看似矛盾的状况,咱们一步步拆解清楚到底为啥会这样。
首先先把你提到的代码补全成完整的错误场景,方便大家理解:
use std::cell::RefCell; struct Foo { val: i32, } impl Foo { fn increment(&mut self) -> Option<i32> { if self.val >= 10 { return None; } self.val += 1; Some(self.val) } } fn main() { let foo = RefCell::new(Foo { val: 0 }); // 这段代码会触发编译错误! while let Some(v) = foo.borrow_mut().increment() { println!("{}", v); } }
你会看到编译器报错说“cannot borrow foo as mutable more than once at a time”,明明每次循环里increment调用完应该就释放借用了啊?问题出在Rust对临时值的生命周期处理规则上:
当你在while let的条件表达式里直接调用foo.borrow_mut().increment()时,borrow_mut()返回的RefMut(RefCell的可变借用守卫)是个临时值。Rust会把这个临时值的生命周期延长到整个while循环的条件判断阶段——也就是说,每次循环中,这个RefMut要等到条件判断完全结束(也就是进入循环体或者退出循环)才会被drop掉。
这就导致了一个问题:第一次循环时,你拿到了一个可变借用,这个借用要等到循环体执行完才会释放;而当第二次循环开始检查条件时,上一次的借用还没被释放,你又去调用borrow_mut(),直接触发了RefCell的借用检查规则,自然就报错了。
那为啥把借用赋值给变量就没问题呢?比如改成这样:
fn main() { let foo = RefCell::new(Foo { val: 0 }); // 这段代码完全正常! while let Some(v) = { let mut borrowed = foo.borrow_mut(); borrowed.increment() } { println!("{}", v); } }
核心原因是代码块限制了借用的生命周期:当你把borrow_mut()的结果赋值给borrowed变量时,这个变量的生命周期只在这个小代码块{}内部。当代码块执行完毕(也就是increment()调用完成并返回结果后),borrowed就会被立刻drop掉,对应的RefCell可变借用也随之释放。这样下一次循环检查条件时,就可以重新安全地调用borrow_mut()了,完全不会有借用冲突。
简单来说,就是把“借了书一直用到循环结束才还”变成了“借了书用完立刻还,下次要用再借”,自然就符合RefCell的规则了。
备注:内容来源于stack exchange,提问作者Tudor

