为何在Rust的if let语句后添加分号可规避借用检查器错误?
我在使用Rust的线程和Arc/Mutex时遇到了借用检查器的报错,以下是编译失败的代码:
use std::sync::{Arc, Mutex}; use std::thread; fn main() { let counter = Arc::new(Mutex::new(0)); { let clone = Arc::clone(&counter); thread::spawn(move || { if let Ok(mut v) = clone.lock() { *v = 10; } }); } }
对应的错误信息:
error[E0597]:
clonedoes not live long enough
--> src/main.rs:9:32
|
9 | if let Ok(mut v) = clone.lock() {
| ^^^^^ borrowed value does not live long enough
...
12 | });
| -clonedropped here while still borrowed
请问为何在if let语句后添加分号就能避免该借用检查器错误?
这事儿得从Rust的表达式规则说起——Rust里几乎所有东西都是表达式,包括if let。咱们一步步拆解两种情况的差异:
没有分号的情况
当你写不带分号的if let时:
if let Ok(mut v) = clone.lock() { *v = 10; }
这个if let是个表达式,它的返回值就是分支里最后一行代码的结果——也就是v(MutexGuard类型)。而MutexGuard本质上持有对clone的借用(毕竟它是通过clone.lock()获取的锁)。
现在,thread::spawn的闭包会把这个表达式的结果当作自己的返回值,这就意味着闭包返回的MutexGuard还在借用clone。但clone是在代码块里创建的,代码块结束后clone就会被销毁,借用检查器通过静态分析认为:clone的生命周期不足以支撑闭包里MutexGuard的借用,于是抛出了报错。
添加分号的情况
当你在if let后面加上分号:
if let Ok(mut v) = clone.lock() { *v = 10; };
分号的作用是把这个表达式转换成一个语句。在Rust里,语句的返回值是()(单元类型),这时候闭包的返回值就变成了(),不再持有任何对clone的借用。
现在clone的生命周期只需要覆盖到lock()调用完成即可——也就是if let内部的逻辑执行完,MutexGuard就会被自动drop,释放对clone的借用。借用检查器确认clone的生命周期满足要求,自然就通过编译了。
补充:另一种等价写法
其实除了加分号,你也可以在if let的分支里显式返回(),效果完全一样:
if let Ok(mut v) = clone.lock() { *v = 10; () }
不过加分号是更符合Rust习惯的写法。
内容的提问来源于stack exchange,提问作者tanagumo

