Prae包装器仅提供不可变访问 无法调用内部Vec的iter_mut方法
问题场景
基于prae crate实现带数据校验的恩尼格玛机逻辑时,转子进位方法advance_rotors触发编译错误,错误点为尝试对RotorConfig类型调用iter_mut()方法获取可变迭代器:
fn advance_rotors(&mut self) { self.rotors.get()[0].rotate(); let mut iterhandle = self.rotors.iter_mut().peekable(); // 编译错误:RotorConfig不存在iter_mut方法 while let Some(el) = iterhandle.next() { match iterhandle.peek_mut() { Some(next_rotor) => match el.should_advance_next() { true => { next_rotor.rotate(); // 该行需要获取next_rotor的可变访问权限 } false => (), }, None => (), } } }
关联类型定义
- 外层
Enigma结构体,其中rotors字段为prae生成的校验包装类型,仅暴露get、set两个内置访问方法:
pub struct Enigma { reflector: Reflector, rotors: RotorConfig, }
RotorConfig通过prae的define!宏生成,内部包装Vec<Rotor>,预设长度为3-4、无重复转子两项校验规则:
prae::define! { #[derive(Debug)] RotorConfig: Vec<Rotor>; validate(RotorConfigError) |config| { match config.len(){ 3..=4 => (), _ => return Err(RotorConfigError::Size) } match config.iter().unique().count(){ 3..=4 =>(), _ => return Err(RotorConfigError::Duplicate) } Ok(()) }; }
错误根因
prae为了保证包装类型内部值始终符合校验规则,默认不开放内部值的可变借用获取入口,仅提供两种安全访问方式:
get():返回内部值的不可变引用,不存在修改值打破校验的风险set(value):整体替换内部值,替换完成后自动执行校验规则,校验不通过则返回错误
由于无法直接获取内部Vec<Rotor>的可变引用,自然无法调用Vec的iter_mut()方法获取可变迭代器修改元素状态。
本场景中转子转动操作仅修改Rotor实例内部的偏移状态,不会改变Vec长度、不会替换转子实例,不会违反预设的两项校验规则,可通过以下三种方案实现需求。
实现方案
方案1:值拷贝后修改回写(兼容性最好)
通过get()获取内部Vec的不可变引用后,克隆一份可变副本,在副本上完成所有转子进位逻辑,最后通过set()方法将修改后的值回写到包装类型,set()会自动执行校验保证值合法:
// 要求Rotor实现Clone trait fn advance_rotors(&mut self) { let mut rotors = self.rotors.get().clone(); // 在可变副本上完成所有修改操作 rotors[0].rotate(); let mut iterhandle = rotors.iter_mut().peekable(); while let Some(el) = iterhandle.next() { if let Some(next_rotor) = iterhandle.peek_mut() { if el.should_advance_next() { next_rotor.rotate(); } } } // 回写修改后的值,自动触发校验 self.rotors.set(rotors).expect("rotor config violated after advancement"); }
该方案存在一次Vec深拷贝开销,但
RotorConfig固定仅存储3-4个Rotor实例,拷贝成本极低,且不需要修改现有类型定义、不需要依赖prae的非默认特性,适配所有prae版本。
方案2:使用可变映射扩展方法(零拷贝开销)
prae 0.12及以上版本提供了with_mut扩展方法(需在Cargo.toml中启用prae的extend特性),支持传入闭包直接获取内部值的可变引用进行操作,闭包执行完成后会自动运行校验逻辑,不需要手动克隆回写:
fn advance_rotors(&mut self) { self.rotors.with_mut(|rotors| { rotors[0].rotate(); let mut iterhandle = rotors.iter_mut().peekable(); while let Some(el) = iterhandle.next() { if let Some(next_rotor) = iterhandle.peek_mut() { if el.should_advance_next() { next_rotor.rotate(); } } } }).expect("rotor config violated after advancement"); }
使用该方法需要保证闭包内的修改最终符合校验规则,本场景仅修改转子内部偏移状态,不会触发校验失败,完全适用。
方案3:拆分静态配置与动态状态(架构优化)
如果转子转动是高频操作,可以将类型的静态配置和动态状态拆分:RotorConfig仅存储不需要修改的转子类型、接线规则等静态配置(仍由prae做校验),将转子的转动偏移等高频修改的动态状态拆分为独立的Vec<RotorState>字段,不放入校验包装类型,直接开放可变访问,从根源上避免高频修改时的校验、拷贝开销。
内容的提问来源于stack exchange,提问作者Brandon Piña

