Rust中嵌套可变与不可变借用冲突问题及惯用解法问询
Rust可变/不可变借用冲突的惯用解决方案
问题复现代码
enum MRE_Enum { variant1, variant2, } struct MRE_Struct { dummy1: Vec<MRE_Enum>, dummy2: isize, } // 私有方法 impl MRE_Struct { fn foo(&mut self) { self.dummy2 += 0; // 仅为示例占位 } fn huh(&mut self) { self.dummy2 -= 0; // 仅为示例占位 } // 注意:这两个方法仅修改dummy2,不涉及dummy1 } // 公有方法 impl MRE_Struct { pub fn bar(&mut self) { for dummy in self.dummy1.iter() { // 不可变借用dummy1 match dummy { MRE_Enum::variant1 => self.foo(), // 错误:尝试可变借用self,但已有不可变借用存在 MRE_Enum::variant2 => self.huh(), // 错误:同上 } } } }
问题核心
遍历结构体的不可变字段dummy1时,循环内调用仅修改另一字段dummy2的可变方法,触发Rust的借用检查错误。已知实现Clone/Copy可以解决,但不符合Rust惯用风格,需要更地道的解决方案。
符合Rust惯用风格的解决方案
1. 缩小不可变借用的范围
先把dummy1的迭代内容提前取出来,让编译器清楚判断我们只是借用dummy1,和dummy2的修改互不干扰:
pub fn bar(&mut self) { // 先把dummy1的元素克隆一份,拿到所有权,脱离对self的借用 let iter = self.dummy1.iter().cloned(); for dummy in iter { match dummy { MRE_Enum::variant1 => self.foo(), MRE_Enum::variant2 => self.huh(), } } }
如果不想克隆,也可以先把迭代器收集成临时向量:
pub fn bar(&mut self) { let items: Vec<_> = self.dummy1.iter().collect(); for dummy in items { match dummy { MRE_Enum::variant1 => self.foo(), MRE_Enum::variant2 => self.huh(), } } }
这么做的核心是让dummy1的不可变借用在进入循环前就结束,循环里的可变借用只针对dummy2,借用检查器就不会报错了。
2. 重构方法参数,明确只修改目标字段
既然foo和huh从头到尾只改dummy2,不如直接把它们改成接受dummy2的可变引用,而不是整个self的可变引用:
impl MRE_Struct { fn foo(dummy2: &mut isize) { *dummy2 += 0; } fn huh(dummy2: &mut isize) { *dummy2 -= 0; } } impl MRE_Struct { pub fn bar(&mut self) { for dummy in self.dummy1.iter() { match dummy { MRE_Enum::variant1 => Self::foo(&mut self.dummy2), MRE_Enum::variant2 => Self::huh(&mut self.dummy2), } } } }
这种做法最直接,明明白白告诉编译器:我们只动dummy2,碰都不碰dummy1,自然不会有冲突。
3. 内部可变性(仅适合特殊场景)
如果你的结构体设计必须要在持有dummy1不可变引用的同时修改dummy2,可以用RefCell实现内部可变性,但要注意这会把借用检查从编译期移到运行期,得自己保证不会出现运行时panic:
use std::cell::RefCell; struct MRE_Struct { dummy1: Vec<MRE_Enum>, dummy2: RefCell<isize>, } impl MRE_Struct { fn foo(&self) { *self.dummy2.borrow_mut() += 0; } fn huh(&self) { *self.dummy2.borrow_mut() -= 0; } } impl MRE_Struct { pub fn bar(&self) { for dummy in self.dummy1.iter() { match dummy { MRE_Enum::variant1 => self.foo(), MRE_Enum::variant2 => self.huh(), } } } }
一般优先用前两种编译期安全的方案,这个只在确实需要共享不可变引用同时改内部状态的时候用。
内容的提问来源于stack exchange,提问作者kesarling
相关产品推荐
相关产品推荐

