Rust中为多个子对象安装可变回调的惯用方法及冲突解决
问题描述
我有一个操作对象数组的算法,调用者需要监听该算法触发的特定事件(对象更新)。以下是简化示例代码:
算法模块
// 包含算法的模块,无需知晓调用者在监听器中的具体逻辑 trait Listener { fn update(&mut self, s: String); } struct Object<L: Listener> { /* 其他字段 */ listener: L, } fn do_stuff<L: Listener>(objects: &mut [Object<L>]) { // 执行操作,过程中可能多次调用每个对象的监听器 objects[0].listener.update("zero".to_string()); objects[1].listener.update("one".to_string()); objects[0].listener.update("zeroes".to_string()); }
调用者调用do_stuff()时,算法会修改对象数组,并在修改过程中调用每个对象的listener,算法模块无需知晓回调的具体逻辑。
主模块
// 主模块 struct MyListener<'a>{ node: &'a mut Vec<String>, prefix: &'static str, } impl<'a> Listener for MyListener<'a> { fn update(&mut self, s: String) { self.node.push(format!("{} {}", self.prefix, s)); } } pub fn main() { let mut strings = Vec::new(); let mut objects = vec![ Object{listener: MyListener{node: &mut strings, prefix: "red"}}, Object{listener: MyListener{node: &mut strings, prefix: "blue"}}, Object{listener: MyListener{node: &mut strings, prefix: "green"}}, ]; do_stuff(&mut objects); }
编译错误
Compiling playground v0.0.1 (/playground) error[E0499]: cannot borrow `strings` as mutable more than once at a time --> src/main.rs:37:43 | 35 | let mut objects = vec![ | _______________________- 36 | | Object{listener: MyListener{node: &mut strings, prefix: "red"}}, | | ------------ first mutable borrow occurs here 37 | | Object{listener: MyListener{node: &mut strings, prefix: "blue"}}, | | ^^^^^^^^^^^^ second mutable borrow occurs here 38 | | Object{listener: MyListener{node: &mut strings, prefix: "green"}}, 39 | | ]; | |_____- first borrow later used here error[E0499]: cannot borrow `strings` as mutable more than once at a time --> src/main.rs:38:43 | 35 | let mut objects = vec![ | _______________________- 36 | | Object{listener: MyListener{node: &mut strings, prefix: "red"}}, | | ------------ first mutable borrow occurs here 37 | | Object{listener: MyListener{node: &mut strings, prefix: "blue"}}, 38 | | Object{listener: MyListener{node: &mut strings, prefix: "green"}}, | | ^^^^^^^^^^^^ second mutable borrow occurs here 39 | | ]; | |_____- first borrow later used here For more information about this error, try `rustc --explain E0499`. error: could not compile `playground` due to 2 previous errors
实际场景与需求
实际场景中,我并非更新Vec,而是调用可变的SVG文档对象的方法添加SVG节点:算法将对象转换为基础图形,每个listener结合对象样式(如线宽、颜色)生成SVG命令并添加到文档中。
当前代码因多次可变借用共享对象触发错误,但所有listener都需要修改同一对象。我不想让do_stuff()返回更新列表再由调用者处理(会产生大量临时向量),请问是否有办法让现有设计可行?
解决方案
方法一:使用内部可变性(RefCell)
单线程场景下,RefCell允许在共享引用内部实现可变借用,能完美适配你的需求。只需把共享对象包裹在RefCell中,让每个MyListener持有一个&RefCell<...>引用即可:
use std::cell::RefCell; // 算法模块保持不变 trait Listener { fn update(&mut self, s: String); } struct Object<L: Listener> { listener: L, } fn do_stuff<L: Listener>(objects: &mut [Object<L>]) { objects[0].listener.update("zero".to_string()); objects[1].listener.update("one".to_string()); objects[0].listener.update("zeroes".to_string()); } // 主模块修改 struct MyListener<'a>{ node: &'a RefCell<Vec<String>>, prefix: &'static str, } impl<'a> Listener for MyListener<'a> { fn update(&mut self, s: String) { // 运行时获取可变借用 let mut node = self.node.borrow_mut(); node.push(format!("{} {}", self.prefix, s)); } } pub fn main() { let strings = RefCell::new(Vec::new()); let mut objects = vec![ Object{listener: MyListener{node: &strings, prefix: "red"}}, Object{listener: MyListener{node: &strings, prefix: "blue"}}, Object{listener: MyListener{node: &strings, prefix: "green"}}, ]; do_stuff(&mut objects); // 输出结果验证 println!("{:?}", strings.borrow()); }
方法二:多线程场景用Arc<Mutex>
如果你的代码涉及多线程,需要线程安全的共享可变访问,可以用Arc<Mutex>实现:
use std::sync::{Arc, Mutex}; // 算法模块保持不变 trait Listener { fn update(&mut self, s: String); } struct Object<L: Listener> { listener: L, } fn do_stuff<L: Listener>(objects: &mut [Object<L>]) { objects[0].listener.update("zero".to_string()); objects[1].listener.update("one".to_string()); objects[0].listener.update("zeroes".to_string()); } // 主模块修改 struct MyListener { node: Arc<Mutex<Vec<String>>>, prefix: &'static str, } impl Listener for MyListener { fn update(&mut self, s: String) { let mut node = self.node.lock().unwrap(); node.push(format!("{} {}", self.prefix, s)); } } pub fn main() { let strings = Arc::new(Mutex::new(Vec::new())); let mut objects = vec![ Object{listener: MyListener{node: Arc::clone(&strings), prefix: "red"}}, Object{listener: MyListener{node: Arc::clone(&strings), prefix: "blue"}}, Object{listener: MyListener{node: Arc::clone(&strings), prefix: "green"}}, ]; do_stuff(&mut objects); println!("{:?}", strings.lock().unwrap()); }
总结
单线程场景优先选择RefCell方案:无需修改算法模块逻辑,所有Listener能安全修改同一共享对象,且不会产生临时向量,完全匹配你的需求。多线程场景则使用Arc<Mutex>保证线程安全。
内容的提问来源于stack exchange,提问作者Bernard
相关产品推荐
相关产品推荐

