如何在Rust的Trait对象集合中获取元素的可变引用
解决Rust中Trait对象集合的可变引用问题
错误1:无法从不可变引用获取可变借用
原代码中Animal trait的as_any()仅返回不可变的&dyn Any,但downcast_mut需要可变引用才能提取具体类型的可变指针。解决方法是给trait Animal添加一个返回可变Any引用的方法:
use std::any::Any; trait Animal { fn as_any(&self) -> &dyn Any; fn as_any_mut(&mut self) -> &mut dyn Any; // 新增可变版本 fn feed(&mut self); fn is_fed(&self) -> bool; }
然后在Dog和Cat的实现中补充这个方法:
// Dog的实现补充 impl Animal for Dog { // ... 原有代码 ... fn as_any_mut(&mut self) -> &mut dyn Any { self } } // Cat的实现补充 impl Animal for Cat { // ... 原有代码 ... fn as_any_mut(&mut self) -> &mut dyn Any { self } }
最后修改Zoo::host方法,改用as_any_mut进行向下转型:
fn host<'a, A: Animal + 'static>(&'a mut self, a: A) -> &'a mut A { self.animals.push(Box::new(a)); let pushed_box = self.animals.last_mut().expect("error"); pushed_box.as_any_mut().downcast_mut::<A>().expect("error") }
错误2:可变引用生命周期冲突
Rust借用检查器禁止同时存在多个可变引用。host返回的&mut A是从&mut self(Zoo的可变引用)派生的,只要这个引用存活,就无法再次可变借用Zoo(比如再次调用host)。
简单解决方案:调整代码顺序
如果不需要在插入过程中操作元素,只需将所有host调用放在操作引用之前:
fn main() { let mut zoo = Zoo::new(); zoo.host(Dog{is_fed:false}); let cat = zoo.host(Cat{is_fed:false}); zoo.host(Cat{is_fed:false}); zoo.host(Dog{is_fed:false}); zoo.host(Cat{is_fed:false}); let dog = zoo.host(Dog{is_fed:false}); zoo.host(Cat{is_fed:false}); // 现在可以正常调用 cat.feed(); dog.feed(); }
灵活解决方案:使用索引+内部可变性
如果需要在插入中间操作元素,可以用RefCell实现内部可变性,通过索引间接获取元素的可变引用:
修改Zoo结构和方法:
use std::cell::{RefCell, RefMut}; struct Zoo { animals: Vec<RefCell<Box<dyn Animal>>> } impl Zoo { fn new() -> Zoo { Zoo{animals: Vec::new()} } fn host<A: Animal + 'static>(&mut self, a: A) -> usize { self.animals.push(RefCell::new(Box::new(a))); self.animals.len() - 1 // 返回元素索引 } // 通过索引获取指定类型的可变引用 fn get_animal_mut<A: Animal + 'static>(&self, idx: usize) -> Option<RefMut<A>> { self.animals.get(idx) .and_then(|cell| cell.try_borrow_mut().ok()) .and_then(|mut boxed| boxed.as_any_mut().downcast_mut::<A>()) .map(|a| RefMut::map(a, |x| x)) } }
对应的main函数:
fn main() { let mut zoo = Zoo::new(); zoo.host(Dog{is_fed:false}); let cat_idx = zoo.host(Cat{is_fed:false}); zoo.host(Cat{is_fed:false}); // 插入操作不受引用影响 zoo.host(Dog{is_fed:false}); zoo.host(Cat{is_fed:false}); let dog_idx = zoo.host(Dog{is_fed:false}); zoo.host(Cat{is_fed:false}); // 通过索引获取并操作元素 if let Some(mut cat) = zoo.get_animal_mut::<Cat>(cat_idx) { cat.feed(); } if let Some(mut dog) = zoo.get_animal_mut::<Dog>(dog_idx) { dog.feed(); } }
完整可运行代码(方案1)
use std::any::Any; trait Animal { fn as_any(&self) -> &dyn Any; fn as_any_mut(&mut self) -> &mut dyn Any; fn feed(&mut self); fn is_fed(&self) -> bool; } struct Dog { is_fed: bool } impl Animal for Dog { fn as_any(&self) -> &dyn Any { self } fn as_any_mut(&mut self) -> &mut dyn Any { self } fn feed(&mut self) { self.is_fed = true; } fn is_fed(&self) -> bool { self.is_fed } } struct Cat { is_fed: bool } impl Animal for Cat { fn as_any(&self) -> &dyn Any { self } fn as_any_mut(&mut self) -> &mut dyn Any { self } fn feed(&mut self) { self.is_fed = true; } fn is_fed(&self) -> bool { self.is_fed } } struct Zoo { animals: Vec<Box<dyn Animal>> } impl Zoo { fn new() -> Zoo { Zoo{animals: Vec::new()} } fn host<'a, A: Animal + 'static>(&'a mut self, a: A) -> &'a mut A { self.animals.push(Box::new(a)); let pushed_box = self.animals.last_mut().expect("error"); pushed_box.as_any_mut().downcast_mut::<A>().expect("error") } } fn main() { let mut zoo = Zoo::new(); zoo.host(Dog{is_fed:false}); let cat = zoo.host(Cat{is_fed:false}); zoo.host(Cat{is_fed:false}); zoo.host(Dog{is_fed:false}); zoo.host(Cat{is_fed:false}); let dog = zoo.host(Dog{is_fed:false}); zoo.host(Cat{is_fed:false}); cat.feed(); dog.feed(); // 验证效果 println!("Cat is fed: {}", cat.is_fed()); println!("Dog is fed: {}", dog.is_fed()); }
内容的提问来源于stack exchange,提问作者Antoine Morel
相关产品推荐
相关产品推荐

