使用RwLock时为何无法将两行操作合并为单语句?
我在试验Rust时遇到了一个非常意外的问题:当用RwLock包裹State状态时,无法将两个操作该状态的语句合并为一条;但直接使用State时该操作却能正常运行。
示例代码如下:
use parking_lot::{RwLock}; pub struct State { pub current: u8, pub history: Vec<u8>, } fn main() { let locked = RwLock::new( State { current: 10, history: Vec::new(), }); // type: lock_api::rwlock::RwLockWriteGuard<parking_lot::raw_rwlock::RawRwLock, playground::State> let mut state = locked.write(); // Works as two statements let old = std::mem::replace(&mut state.current, 5); state.history.push(old); // Fails as one statement // Error: cannot borrow `state` as mutable more than once at a time state.history.push(std::mem::replace( &mut state.current, 5, )); }
有人能解释这一现象吗?
注:我已看过类似《Error while trying to borrow 2 fields from a struct wrapped in RefCell》的问题,但这并非同一问题。该现象仅在嵌套语句且使用RwLock时出现,是我仅在Rust中遇到的情况。
原因解释
核心问题出在**RwLockWriteGuard的DerefMut实现**与Rust借用检查器的作用逻辑上:
分开语句的正常逻辑:
执行let old = std::mem::replace(&mut state.current, 5);时,&mut state.current会通过DerefMut获取内部State的可变引用,进而借用current字段。这个借用在mem::replace执行完成后立即释放——因为old已经拿到了替换后的值。后续调用state.history.push(old)时,借用的是history字段,此时current的借用已结束,不会产生冲突。合并语句的冲突逻辑:
在state.history.push(std::mem::replace(&mut state.current, 5))中,Rust会先解析所有参数:- 为调用
state.history.push,需要通过DerefMut获取State的可变引用,进而借用history字段; - 为执行
mem::replace,需要再次通过DerefMut获取State的可变引用,进而借用current字段。
但借用检查器无法“看穿”RwLockWriteGuard的包装,它只会看到你多次尝试对Guard本身进行可变借用(因为每次DerefMut都需要先可变借用Guard),而Rust不允许同一值被同时多次可变借用,因此触发错误。
- 为调用
直接使用State的差异:
当直接操作State实例时,Rust支持字段级拆分可变借用——只要借用的是不同字段,就允许同时存在多个可变借用。但RwLockWriteGuard是一个中间包装类型,它的DerefMut需要先可变借用Guard本身,这导致借用检查器无法细化到内部结构体的字段级别,只能以Guard为单位进行借用判定。
简单来说,RwLockWriteGuard的存在阻断了借用检查器对内部结构体字段的精细化分析,使得原本合法的跨字段可变借用被判定为冲突。
内容的提问来源于stack exchange,提问作者magum

