Rust中生命周期与闭包捕获变量的交互及编译错误疑问
我正在通过《The Rust Programming Language》(Rust官方书籍)学习Rust,遇到了如下问题:
问题代码
fn main() { let mut s = String::from("Hello"); let mut add_suffix = || s.push_str(" world"); println!("{s}"); add_suffix(); }
编译错误信息
error[E0502]: cannot borrow `s` as immutable because it is also borrowed as mutable --> src/main.rs:4:16 | 3 | let mut add_suffix = || s.push_str(" world"); | -- - first borrow occurs due to use of `s` in closure | | | mutable borrow occurs here 4 | println!("{s}"); | ^ immutable borrow occurs here 5 | add_suffix(); | ---------- mutable borrow later used here | = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
我理解add_suffix闭包获取了s的可变引用(因push_str需要可变引用),其生命周期从闭包定义开始到调用结束,但我不明白为何在调用闭包前就会触发借用冲突,Rust为何要这样强制约束,以及这样做背后的不安全点是什么?如果假设可变引用的生命周期仅在闭包调用期间存在,我看不出会有什么问题。
问题解答
1. 闭包的借用发生在定义时,而非调用时
Rust闭包在创建的瞬间就会捕获目标变量的引用,而不是等到调用闭包时才去借用。对于你的代码,当你写下let mut add_suffix = || s.push_str(" world");这一行时,闭包已经拿到了s的可变引用,并且这个引用会一直被闭包持有,直到闭包本身被销毁(也就是main函数结束前)。
本质上,闭包是个匿名结构体,它会把捕获的引用作为自己的字段保存起来。只要闭包还活着,这个引用就不会被释放,自然也不会允许其他冲突的借用存在。
2. 这种约束背后的安全原因
Rust的借用规则核心是:同一时间,要么存在一个可变引用,要么存在多个不可变引用,二者绝对不能共存。如果允许你在闭包定义后、调用前对s做不可变借用,会直接破坏这个规则,引发内存安全问题:
举个极端的反例,假设Rust允许这种操作,就可能出现以下危险场景:
// 仅作为反例,实际编译不通过 fn main() { let mut s = String::from("Hello"); let mut add_suffix = || s.push_str(" world"); let s_ref = &s; // 假设允许创建这个不可变引用 add_suffix(); // 闭包用可变引用修改s,此时String可能因为扩容发生内存重分配 println!("{}", s_ref); // s_ref现在指向的是已经被释放的内存,变成悬垂引用 }
一旦出现悬垂引用,后续访问它会导致未定义行为,这正是Rust要彻底杜绝的情况。
3. 解决方法
要解决这个问题很简单,调整代码顺序,先完成对s的不可变借用操作,再定义闭包:
fn main() { let mut s = String::from("Hello"); println!("{s}"); // 先完成不可变借用和使用 let mut add_suffix = || s.push_str(" world"); // 此时才获取可变引用 add_suffix(); }
如果你的场景必须先定义闭包,之后还要操作s,可以考虑使用RefCell这类内部可变性容器,但这会带来运行时的借用检查开销,只适合确实需要的场景。
内容的提问来源于stack exchange,提问作者Kami SM

