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

Rust观察者模式实现遇类型不匹配错误,求解决方案

问题分析

编译错误的核心是泛型 trait 对象的类型不兼容:你存储的是Arc<dyn Subscriber<dyn Message>>,但注册的是Arc<dyn Subscriber<T>>(T是具体的Message类型)。Subscriber<T>和Subscriber<dyn Message>是不同的trait对象类型,Rust不允许直接转换,因为泛型trait的类型参数不具备协变性,且无法保证类型安全。

解决方案

引入一个类型擦除的中间trait,将具体类型的订阅者统一转换成这个无泛型的trait对象存储,同时在内部处理类型转换和安全检查。

步骤1:修改Message trait,添加实例方法

为了在类型转换失败时能输出有用信息,给Message添加一个实例方法id():

pub trait Message {
    fn id() -> u8 where Self: Sized;
    // 实例方法,返回当前消息类型的id
    fn id(&self) -> u8 {
        <Self as Message>::id()
    }
}

步骤2:定义类型擦除的ErasedSubscriber trait

这个trait不依赖具体消息类型,只处理dyn Message:

pub trait ErasedSubscriber {
    fn receive_erased(&self, msg: &Box<dyn Message>);
}

步骤3:为所有Subscriber<T>自动实现ErasedSubscriber

通过泛型实现,让任何实现了Subscriber<T>的类型自动具备ErasedSubscriber的能力,内部处理类型向下转换:

impl<T, S> ErasedSubscriber for S
where
    T: Message,
    S: Subscriber<T>,
{
    fn receive_erased(&self, msg: &Box<dyn Message>) {
        // 尝试将动态类型的消息向下转换为订阅者期望的具体类型
        let concrete_msg = msg.downcast_ref::<T>().unwrap_or_else(|| {
            panic!(
                "订阅者收到不匹配的消息类型:期望ID {},实际ID {}",
                T::id(),
                msg.id()
            )
        });
        // 调用订阅者的实际处理方法
        self.receive(concrete_msg);
    }
}

步骤4:简化Subscriber trait的方法参数

原receive方法的参数是&Box<T>,冗余且低效,改成直接接收&T:

pub trait Subscriber<T>
where
    T: Message,
{
    fn receive(&self, msg: &T);
}

步骤5:修改Publisher的存储结构

将订阅者存储为Arc<dyn ErasedSubscriber>,而不是泛型的Subscriber:

pub struct Publisher<'a, 'b> {
    subscribers: HashMap<
        u8,
        (
            fn(&[u8]) -> Box<dyn Message + 'a>,
            Arc<dyn ErasedSubscriber + 'b>,
        ),
    >,
}

步骤6:调整register_subscriber方法

将传入的具体订阅者转换为ErasedSubscriber trait对象:

pub fn register_subscriber<T>(&mut self, subscriber: Arc<dyn Subscriber<T> + 'b>)
where
    T: Message + 'a,
    T: for<'c> From<&'c [u8]>;
{
    self.subscribers.insert(
        T::id(),
        (
            |data| Box::new(T::from(data)),
            subscriber as Arc<dyn ErasedSubscriber + 'b>,
        ),
    );
}

步骤7:修改publish方法

调用ErasedSubscriber的receive_erased方法:

pub fn publish(&self, id: u8, data: &[u8]) {
    if let Some((message_factory, subscriber)) = self.subscribers.get(&id) {
        let message = message_factory(data);
        subscriber.receive_erased(&message);
    }
}

调整具体订阅者的实现

对应修改ConcreteSubscriber的receive方法参数:

impl Subscriber<ConcreteMessage> for ConcreteSubscriber {
    fn receive(&self, _msg: &ConcreteMessage) {
        // 处理具体消息逻辑
    }
}
完整可编译代码
use std::{collections::HashMap, sync::Arc};

pub trait Message {
    fn id() -> u8 where Self: Sized;
    fn id(&self) -> u8 {
        <Self as Message>::id()
    }
}

pub trait Subscriber<T>
where
    T: Message,
{
    fn receive(&self, msg: &T);
}

pub trait ErasedSubscriber {
    fn receive_erased(&self, msg: &Box<dyn Message>);
}

impl<T, S> ErasedSubscriber for S
where
    T: Message,
    S: Subscriber<T>,
{
    fn receive_erased(&self, msg: &Box<dyn Message>) {
        let concrete_msg = msg.downcast_ref::<T>().unwrap_or_else(|| {
            panic!(
                "订阅者收到不匹配的消息类型:期望ID {},实际ID {}",
                T::id(),
                msg.id()
            )
        });
        self.receive(concrete_msg);
    }
}

pub struct Publisher<'a, 'b> {
    subscribers: HashMap<
        u8,
        (
            fn(&[u8]) -> Box<dyn Message + 'a>,
            Arc<dyn ErasedSubscriber + 'b>,
        ),
    >,
}

impl<'a, 'b> Publisher<'a, 'b> {
    pub fn new() -> Self {
        Self {
            subscribers: HashMap::new(),
        }
    }

    pub fn register_subscriber<T>(&mut self, subscriber: Arc<dyn Subscriber<T> + 'b>)
    where
        T: Message + 'a,
        T: for<'c> From<&'c [u8]>;
    {
        self.subscribers.insert(
            T::id(),
            (
                |data| Box::new(T::from(data)),
                subscriber as Arc<dyn ErasedSubscriber + 'b>,
            ),
        );
    }

    pub fn publish(&self, id: u8, data: &[u8]) {
        if let Some((message_factory, subscriber)) = self.subscribers.get(&id) {
            let message = message_factory(data);
            subscriber.receive_erased(&message);
        }
    }
}

pub struct ConcreteMessage {
    // 成员省略
}

impl Message for ConcreteMessage {
    fn id() -> u8 {
        12
    }
}

impl From<&[u8]> for ConcreteMessage {
    fn from(_data: &[u8]) -> Self {
        Self {}
    }
}

pub struct ConcreteSubscriber {
    // 成员省略
}

impl Subscriber<ConcreteMessage> for ConcreteSubscriber {
    fn receive(&self, _msg: &ConcreteMessage) {
        // 处理消息逻辑
    }
}

fn main() {
    let mut publisher = Publisher::new();
    let subscriber = Arc::new(ConcreteSubscriber {});

    publisher.register_subscriber(subscriber);
    publisher.publish(12, &[0, 1, 2, 3]);
}
关键说明
  • 类型擦除:通过ErasedSubscriber将不同泛型参数的Subscriber统一成同一个trait对象,解决了存储时的类型不兼容问题。
  • 类型安全:在receive_erased中通过downcast_ref做类型检查,确保订阅者只会收到自己绑定的消息类型(由于Publisher是按消息ID注册和发布的,理论上不会出现类型不匹配,这里的panic是兜底处理)。
  • 效率优化:将&Box<T>改成&T,避免了不必要的Box操作,提升性能。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 03:22:07