You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 20:41:07