Rust中含Box<dyn FnMut(&mut Self)>字段的结构体调用报错问题
问题场景
定义了UpgradeProperties结构体,其中包含一个Box<dyn FnMut(&mut Self)>类型的buy字段,用于为不同实例配置自定义购买逻辑。但编写call_buy函数调用该字段时,触发了E0499可变借用冲突错误。
错误代码
pub struct UpgradeProperties { pub name: String, pub description: String, pub cost: u32, pub cost_increase: u8, pub icon: Texture2D, pub current: f32, pub buy: Box<dyn FnMut(&mut Self)>, } impl UpgradeProperties { pub fn call_buy(&mut self) { (self.buy)(self); } } // buy字段初始化示例 buy: Box::new(move |s| { s.cost *= s.cost_increase as u32; s.current += 1.0; }),
错误信息
error[E0499]: cannot borrow
*selfas mutable more than once at a time
--> src/upgrade.rs:22:20
|
22 | (self.buy)(self);
| ---------- ^^^^ second mutable borrow occurs here
| |
| first mutable borrow occurs here
| first borrow later used by callFor more information about this error, try
rustc --explain E0499.
问题原因
调用self.buy时已经对self的buy字段进行了可变借用,紧接着又将&mut self作为参数传递给该闭包,导致Rust借用检查器检测到两次同时存在的可变借用,违反了Rust的借用规则。
解决方案
通过std::mem::replace临时取出buy字段,避免同时借用冲突。修改后的call_buy函数如下:
impl UpgradeProperties { pub fn call_buy(&mut self) { // 创建临时闭包作为占位,替换出原有的buy函数 let temp_buy = Box::new(|_| {}); let mut buy = std::mem::replace(&mut self.buy, temp_buy); // 调用原buy函数,此时self的buy字段已被临时替换,无借用冲突 buy(self); // 将原buy函数放回结构体 self.buy = buy; } }
原理说明
std::mem::replace会将self.buy的值替换为传入的临时闭包,同时返回原来的buy函数。此时调用buy(self)时,self的buy字段是临时闭包,而我们操作的是取出的原函数,不存在重叠的可变借用,因此通过了借用检查器的验证。
替代思路(可选)
如果buy闭包不需要修改结构体中的buy字段本身,也可以将结构体拆分为两部分:一部分存放业务数据,另一部分存放buy闭包,从根源上避免借用冲突。例如:
// 存放业务数据的结构体 pub struct UpgradeData { pub name: String, pub description: String, pub cost: u32, pub cost_increase: u8, pub icon: Texture2D, pub current: f32, } // 包含逻辑的结构体 pub struct UpgradeProperties { data: UpgradeData, buy: Box<dyn FnMut(&mut UpgradeData)>, } impl UpgradeProperties { pub fn call_buy(&mut self) { (self.buy)(&mut self.data); } }
这种方式将数据与逻辑分离,调用时只需要借用data字段,不会与buy字段的借用产生冲突。
内容的提问来源于stack exchange,提问作者Andy Galvez

