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

嵌入式Rust能否实现观察者模式?C/C++开发者的转译困惑

从C++嵌入式观察者模式迁移到Rust的困惑

我是嵌入式软件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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 05:54:53