嵌入式Rust能否实现观察者模式?C/C++开发者的转译困惑
我是嵌入式软件C/C++开发者,最近在学习Rust,尝试将工作中使用的组件通信模式迁移过来。我们的应用里大量使用观察者模式实现组件间通信:需要对应行为的组件会继承Observer和Publisher泛型类,所有操作都是静态的,Publisher在编译时就知晓存储Observer引用的数组大小,程序启动时完成所有实例化,在空闲任务运行前通过多态完成绑定。
C++示例
这是头文件库,隐藏了实现细节:
template <typename T> class Observer { public: virtual void Update(const &T data) = 0; }; template <typename T, uint8_t N = 1u> class Publisher { public: bool Subscribe(Observer<T>& observer); bool Unsubscribe(const Observer<T>& observer); protected: void Notify(const T& data); private: std::array<Observer<T>*, N> observers; };
本质就是常规观察者模式:Publisher存储绑定的Listener引用数组,调用notify时通知所有Observer。组件可根据角色继承这些类,一个组件可以是一种或多种类型的Observer和/或Publisher。
Rust实现尝试
我试着在Rust里复现该模式,但很快发现和语言范式冲突,以下是简单示例:
trait Listener<T> { fn update(&self, data: &T); } struct Publisher<'a, T, const N: usize = 1> { listeners: [Option<&'a dyn Listener<T>>; N], } impl<'a, T, const N: usize> Publisher<'a, T, N> { fn new() -> Self { Self { listeners: [None; N], } } fn subscribe(&mut self, listener: &'a dyn Listener<T>) { for slot in self.listeners.iter_mut() { if slot.is_none() { *slot = Some(listener); } } } fn notify(&self, data: &T) { for listener in self.listeners.iter().flatten() { listener.update(data); } } }
为Component实现该模式:
#[derive(Default)] struct Component { data: Cell<u32>, flag: Cell<bool>, some_counter: u8, } impl Component { fn update_counter(&mut self) { self.some_counter += 1; } } impl Listener<u32> for Component { fn update(&self, data: &u32) { self.data.replace(*data); } } impl Listener<bool> for Component { fn update(&self, data: &bool) { self.flag.replace(*data); } } fn main() { // Listener let listener = Component::default(); // Publisher u32 let mut publisher_u32: Publisher<'_, u32> = Publisher::new(); // Publisher bool let mut publisher_bool: Publisher<'_, bool> = Publisher::new(); publisher_u32.subscribe(&listener); publisher_bool.subscribe(&listener); publisher_u32.notify(&0); publisher_bool.notify(&false); }
但这显然不是正确的实现方式:向Publisher传入不可变引用后,后续无法修改Component——Rust不允许并发可变引用,例如调用Component的&mut self方法会编译失败:
#[derive(Default)] struct Component { data: Cell<u32>, flag: Cell<bool>, some_counter: u8, } impl Component { fn update_counter(&mut self) { self.some_counter += 1; } } ... // main函数末尾这段代码无法编译 listener.update_counter();
疑问
我感觉这种组件通信方式在Rust里走不通,开发起来很麻烦,尤其是系统架构已经这么设定的情况下。我知道这源于Rust的所有权原则,但Publisher无法持有Listener的可变引用,Listener的update方法也不能接收&mut self,导致更新过程受限:只能通过Cell修改成员值,无法在Listener内部同步处理新值,不得不异步延后处理逻辑。
我的思路是不是错了?还是遗漏了某些关键要点?
解决方案:适配Rust所有权模型的设计调整
你核心的问题是试图直接照搬C的继承式观察者模式,但Rust的所有权、借用规则与trait系统和C类继承有本质区别,需要换思路适配:
1. 用内部可变性替代全局可变引用
如果组件需要在update中执行可变操作,不必局限于&self,可以用RefCell(单线程场景)或Mutex/RwLock(多线程嵌入式场景,注意锁开销)实现内部可变性。这样update仍使用&self,但能安全修改内部状态:
use std::cell::{Cell, RefCell}; #[derive(Default)] struct Component { data: Cell<u32>, flag: Cell<bool>, some_counter: RefCell<u8>, // 替换为RefCell实现内部可变 } impl Component { fn update_counter(&self) { // 改为&self方法 *self.some_counter.borrow_mut() += 1; } } impl Listener<u32> for Component { fn update(&self, data: &u32) { self.data.replace(*data); self.update_counter(); // 直接在update内调用,无需异步 } }
这种方式下,即使Publisher持有&Component,你也能通过&self修改状态,Rust的借用检查会保证单线程安全。
2. 用智能指针灵活管理引用
如果确实需要保留&mut self方法,可以将组件放入Rc(单线程)或Arc(多线程)中,让Publisher持有Rc<Component>而非直接引用。这样既可以共享所有权,又能通过Rc::get_mut(无其他强引用时)或结合RefCell获取可变权限:
use std::rc::Rc; use std::cell::{Cell, RefCell}; trait Listener<T> { fn update(&self, data: &T); } struct Publisher<T, const N: usize = 1> { listeners: [Option<Rc<dyn Listener<T>>>; N], // 用Rc持有观察者 } impl<T, const N: usize> Publisher<T, N> { fn new() -> Self { Self { listeners: [None; N], } } fn subscribe(&mut self, listener: Rc<dyn Listener<T>>) { for slot in self.listeners.iter_mut() { if slot.is_none() { *slot = Some(listener); break; // 避免填满所有slot } } } fn notify(&self, data: &T) { for listener in self.listeners.iter().flatten() { listener.update(data); } } } #[derive(Default)] struct Component { data: Cell<u32>, flag: Cell<bool>, some_counter: u8, } impl Component { fn update_counter(&mut self) { self.some_counter += 1; } } impl Listener<u32> for Component { fn update(&self, data: &u32) { self.data.replace(*data); } } fn main() { let listener = Rc::new(RefCell::new(Component::default())); let mut publisher_u32: Publisher<u32> = Publisher::new(); publisher_u32.subscribe(Rc::clone(&listener)); publisher_u32.notify(&0); // 获取可变引用修改组件 listener.borrow_mut().update_counter(); }
3. 采用函数式观察者模式(Rust风格)
Rust更推崇组合而非继承,你可以将观察者逻辑替换为闭包或函数对象,更灵活且避免生命周期问题:
struct Publisher<T, const N: usize = 1> { listeners: [Option<Box<dyn Fn(&T)>>; N], } impl<T, const N: usize> Publisher<T, N> { fn new() -> Self { Self { listeners: [None; N], } } fn subscribe(&mut self, callback: impl Fn(&T) + 'static) { for slot in self.listeners.iter_mut() { if slot.is_none() { *slot = Some(Box::new(callback)); break; } } } fn notify(&self, data: &T) { for callback in self.listeners.iter().flatten() { callback(data); } } } fn main() { let mut some_counter = 0u8; let mut data = 0u32; let mut flag = false; let mut publisher_u32: Publisher<u32> = Publisher::new(); publisher_u32.subscribe(|d| { data = *d; some_counter += 1; // 直接在闭包内修改状态 }); let mut publisher_bool: Publisher<bool> = Publisher::new(); publisher_bool.subscribe(|f| { flag = *f; }); publisher_u32.notify(&100); publisher_bool.notify(&true); }
4. 嵌入式场景的轻量化优化
如果是资源受限的嵌入式系统,可避免Rc/RefCell的开销:
- 用
unsafe手动管理引用(需确保无悬空引用和数据竞争) - 提前初始化静态全局组件,使用
&'static引用规避生命周期:
use std::cell::Cell; trait Listener<T> { fn update(&self, data: &T); } struct Publisher<T, const N: usize = 1> { listeners: [Option<&'static dyn Listener<T>>; N], } impl<T, const N: usize> Publisher<T, N> { const fn new() -> Self { Self { listeners: [None; N], } } fn subscribe(&mut self, listener: &'static dyn Listener<T>) { for slot in self.listeners.iter_mut() { if slot.is_none() { *slot = Some(listener); break; } } } fn notify(&self, data: &T) { for listener in self.listeners.iter().flatten() { listener.update(data); } } } static mut COMPONENT: Component = Component { data: Cell::new(0), flag: Cell::new(false), some_counter: Cell::new(0), }; struct Component { data: Cell<u32>, flag: Cell<bool>, some_counter: Cell<u8>, } impl Component { fn update_counter(&self) { self.some_counter.set(self.some_counter.get() + 1); } } impl Listener<u32> for Component { fn update(&self, data: &u32) { self.data.replace(*data); self.update_counter(); } } fn main() { let mut publisher_u32: Publisher<u32> = Publisher::new(); unsafe { publisher_u32.subscribe(&COMPONENT); } publisher_u32.notify(&0); unsafe { COMPONENT.update_counter(); } }
总结
你的思路没有错,只是Rust的设计哲学与C++不同,不能直接照搬继承式观察者模式。核心是利用内部可变性、智能指针或函数式闭包适配Rust的所有权规则,嵌入式场景可根据资源情况选择轻量化实现。
内容的提问来源于stack exchange,提问作者user26664077

