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

Rust观察者模式中持有自捕获闭包的生命周期问题求解

问题

我在实现观察者设计模式时,尝试用闭包作为回调,但遇到了生命周期相关的编译错误。代码如下:

pub type WriteCallback = Box<dyn FnMut(u8, u8) -> u8>;

pub struct AddressBus {
    on_write_listeners: HashMap<usize, WriteCallback>,
}

impl AddressBus {
    pub fn on_write(&mut self, address: usize, callback: WriteCallback) {
        self.on_write_listeners.insert(address, callback);
    }
}

// component.rs

pub struct Component {
    value: u8,
    address_bus: Rc<RefCell<AddressBus>>,
}

impl Component {
    pub fn new(address_bus: Rc<RefCell<AddressBus>>) -> Self {
        Component {
            value: 0,
            address_bus: address_bus.clone(),
        }
    }

    pub fn init(&mut self) {
        self.address_bus.clone().borrow_mut().on_write(0xFF54, Box::new(|old, new| {
            // 我想在这里使用self,例如:self.value = new;
            new
        }));

        self.address_bus.clone().borrow_mut().on_write(0xFF55, Box::new(|old, new| {
            // 我想在这里使用self
            new
        }));
    }
 }

编译时出现以下错误:

32 |       pub fn init(&mut self) {
   |                   - 我们将此引用的生命周期称为 `'1`
33 |           self.address_bus.clone().borrow_mut().on_write(0xFF54, Box::new(|old, new| {
   |  ________________________________________________________________^
34 | |             // 我想在这里使用self
35 | |             self.value = new;
36 | |             new
37 | |         }));
   | |__________^ 强制转换要求 `'1` 必须比 `'static` 存活更久

我使用wasm-bindgen,无法在结构体中使用生命周期引用。尝试过move闭包,虽然能编译,但会消耗self,导致后续无法再使用该结构体。请问有没有解决方法,或者是否需要更换设计模式?

解决方案

方法一:将Component自身包装为Rc

核心思路是让闭包持有Component的共享可变引用,既满足闭包的'static生命周期要求,又不会消耗self。

调整代码如下:

  1. 修改Component的定义和构造函数,让外部通过Rc<RefCell<Component>>持有实例:
use std::cell::RefCell;
use std::rc::Rc;

pub struct Component {
    value: u8,
    address_bus: Rc<RefCell<AddressBus>>,
}

impl Component {
    // 构造函数返回Rc<RefCell<Self>>
    pub fn new(address_bus: Rc<RefCell<AddressBus>>) -> Rc<RefCell<Self>> {
        let component = Component {
            value: 0,
            address_bus: address_bus.clone(),
        };
        Rc::new(RefCell::new(component))
    }

    pub fn init(self: Rc<RefCell<Self>>) {
        // clone一份Rc供闭包捕获
        let self_clone = self.clone();
        self.borrow().address_bus.clone().borrow_mut().on_write(0xFF54, Box::new(move |old, new| {
            // 通过RefCell获取可变引用修改value
            self_clone.borrow_mut().value = new;
            new
        }));

        let self_clone2 = self.clone();
        self.borrow().address_bus.clone().borrow_mut().on_write(0xFF55, Box::new(move |old, new| {
            self_clone2.borrow_mut().value = new;
            new
        }));
    }
}
  1. 使用方式:
let address_bus = Rc::new(RefCell::new(AddressBus { on_write_listeners: HashMap::new() }));
let component = Component::new(address_bus);
component.init();

原理说明:

  • Rc允许多个所有者共享同一个Component实例,闭包clone的Rc和外部实例都是所有者,不会消耗原实例。
  • RefCell提供内部可变性,允许在持有共享引用的同时修改内部数据,适配FnMut闭包的要求。
  • 闭包捕获的Rc<RefCell<Component>>生命周期为'static,满足WriteCallback的 trait 约束。

方法二:调整设计模式,使用消息传递替代闭包回调

如果不想使用Rc<RefCell>嵌套,可以将观察者模式改为消息驱动,更贴合wasm的异步事件模型:

  1. 定义消息类型,让AddressBus触发事件时发送消息:
// 引入消息通道库flume(需在Cargo.toml添加依赖)
use flume;

// 定义总线消息类型
pub enum BusMessage {
    Write(usize, u8, u8),
}

pub struct AddressBus {
    sender: flume::Sender<BusMessage>,
}

impl AddressBus {
    pub fn new(sender: flume::Sender<BusMessage>) -> Self {
        AddressBus { sender }
    }

    // 模拟写入操作,发送消息通知观察者
    pub fn write(&mut self, addr: usize, old_val: u8, new_val: u8) {
        let _ = self.sender.send(BusMessage::Write(addr, old_val, new_val));
    }
}
  1. Component监听消息通道处理事件:
impl Component {
    pub fn new(address_bus: Rc<RefCell<AddressBus>>, receiver: flume::Receiver<BusMessage>) -> Self {
        // 在wasm异步环境中启动监听任务
        wasm_bindgen_futures::spawn_local(async move {
            while let Ok(msg) = receiver.recv_async().await {
                match msg {
                    BusMessage::Write(0xFF54, _, new_val) => {
                        // 处理0xFF54地址的写入逻辑
                    }
                    BusMessage::Write(0xFF55, _, new_val) => {
                        // 处理0xFF55地址的写入逻辑
                    }
                    _ => {}
                }
            }
        });

        Component {
            value: 0,
            address_bus,
        }
    }
}

这种方式彻底避免了闭包捕获self的生命周期问题,通过消息通道解耦AddressBus和Component,但需要处理异步任务的生命周期管理。

内容的提问来源于stack exchange,提问作者Nhyarlathotep

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 05:55:35