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。
调整代码如下:
- 修改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 })); } }
- 使用方式:
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的异步事件模型:
- 定义消息类型,让
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)); } }
- 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
相关产品推荐
相关产品推荐

