Rust/Serde 序列化带多态payload的事件 解决trait对象安全报错
我在开发中需要定义带有多态payload属性的事件结构,初始实现代码如下:
use serde::{Deserialize, Serialize}; #[derive(Debug, Deserialize, Serialize)] pub struct Event<'a> { pub topic: String, pub key: Option<String>, pub payload: Box<dyn EventPayload<'a>>, }
我对payload类型的唯一要求是支持通过serde_json完成序列化,因此定义了如下约束trait:
pub trait EventPayload<'a> : Debug + Deserialize<'a> + Serialize {}
但该实现无法通过编译,编译器抛出E0038错误,提示EventPayload trait无法构造为对象,不满足对象安全要求,无法生成动态调用所需的vtable,报错信息如下:
error[E0038]: the trait `EventPayload` cannot be made into an object --> pong/src/event.rs:12:26 | 12 | pub payload: Box<dyn EventPayload<'a>>, | ^^^^^^^^^^^^^^^^^^^^ `EventPayload` cannot be made into an object | note: for a trait to be "object safe" it needs to allow building a vtable to allow the call to be resolvable dynamically
我已知可以使用枚举类型实现该需求,示例代码如下:
enum Event { EventA(EventAPayload), EventB(EventBPayload), }
但我不希望采用该方案:枚举实现需要硬编码所有应用外部定义的payload类型(例如来自schema registry的类型),扩展性不足。请问Rust中实现该需求的正确设计模式是什么?
原写法编译失败的核心原因是Serialize和Deserialize本身就不属于对象安全的trait:serde的序列化/反序列化trait在设计上依赖编译期确定的具体类型信息,包含泛型方法,无法生成动态分发需要的vtable,直接将其作为trait的超约束必然触发E0038错误,这个限制没有办法直接绕过,需要根据业务场景选择对应的实现模式:
- 方案一:直接使用
serde_json::Value作为统一payload类型
如果不需要在内存中强绑定payload的具体业务类型,只要求能承载任意可被serde_json序列化/反序列化的内容,这是成本最低、扩展性最好的方案。serde_json::Value是serde提供的动态JSON结构类型,天生支持任意合法JSON值的存取,不需要额外定义trait或做类型注册:
use serde::{Deserialize, Serialize}; use serde_json::Value; #[derive(Debug, Deserialize, Serialize)] pub struct Event { pub topic: String, pub key: Option<String>, pub payload: Value, }
使用时可以直接将任意实现了Serialize的类型转为Value存入,取出时再按需反序列化为具体业务类型即可:
// 存入任意自定义payload let event = Event { topic: "user.created".into(), key: Some("123".into()), payload: serde_json::to_value(UserCreatedPayload { id: 123, name: "test_user".into() }).unwrap() }; // 按需转换为强类型 let typed_payload: UserCreatedPayload = serde_json::from_value(event.payload).unwrap();
- 方案二:类型擦除trait + 全局注册表,实现强类型多态
如果需要保留trait抽象、支持在内存中以trait object形式操作payload,同时不希望硬编码所有payload类型,可以手动实现对象安全的trait,通过全局类型注册表支持外部类型的反序列化扩展:
首先定义对象安全的payload基础trait,注意不要直接继承Deserialize,只保留对象安全的方法:
use serde::{Deserialize, Serialize, de::DeserializeOwned}; use std::any::Any; use std::collections::HashMap; use once_cell::sync::Lazy; use std::sync::RwLock; use serde_json::Value; // 全局反序列化注册表,存储各payload类型对应的反序列化函数 static DESERIALIZERS: Lazy<RwLock<HashMap<&'static str, fn(&Value) -> Box<dyn EventPayload>>>> = Lazy::new(|| RwLock::new(HashMap::new())); pub trait EventPayload: Debug + Any + Serialize { // 返回当前payload的全局唯一类型标识,用于反序列化时匹配 fn payload_type(&self) -> &'static str; // 提供向下转型方法,支持运行时转为具体类型 fn as_any(&self) -> &dyn Any; fn as_any_mut(&mut self) -> &mut dyn Any; } // 为所有满足约束的类型自动实现EventPayload,无需手动为每个payload写impl impl<T: Debug + Serialize + DeserializeOwned + Any + 'static> EventPayload for T { fn payload_type(&self) -> &'static str { std::any::type_name::<T>() } fn as_any(&self) -> &dyn Any { self } fn as_any_mut(&mut self) -> &mut dyn Any { self } } // 提供payload类型注册入口,外部crate定义的类型只需在程序初始化时调用即可接入 pub fn register_payload<T: EventPayload + DeserializeOwned + 'static>() { let type_name = std::any::type_name::<T>(); let deserialize_fn: fn(&Value) -> Box<dyn EventPayload> = |val| { Box::new(serde_json::from_value::<T>(val.clone()).unwrap()) }; DESERIALIZERS.write().unwrap().insert(type_name, deserialize_fn); }
对应的Event结构调整为如下实现,序列化时自动携带类型标识,反序列化时通过注册表查找对应构造函数:
#[derive(Debug, Serialize)] pub struct Event { pub topic: String, pub key: Option<String>, // 序列化时自动写入类型标识,反序列化时靠这个字段找对应构造函数 payload_type: &'static str, pub payload: Box<dyn EventPayload>, } impl Event { pub fn new(topic: String, key: Option<String>, payload: Box<dyn EventPayload>) -> Self { Self { payload_type: payload.payload_type(), topic, key, payload, } } } // 手动实现Deserialize,走注册表逻辑 impl<'de> Deserialize<'de> for Event { fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: serde::Deserializer<'de>, { #[derive(Deserialize)] struct EventHelper { topic: String, key: Option<String>, payload_type: String, payload: Value, } let helper = EventHelper::deserialize(deserializer)?; let deserialize_fn = DESERIALIZERS.read().unwrap() .get(helper.payload_type.as_str()) .ok_or_else(|| serde::de::Error::custom(format!("unregistered payload type: {}", helper.payload_type)))? .clone(); let payload = deserialize_fn(&helper.payload); Ok(Event { topic: helper.topic, key: helper.key, payload_type: payload.payload_type(), payload, }) } }
这个方案下,无论是当前crate还是外部依赖定义的payload类型,只需要在程序初始化阶段调用一次register_payload::<YourCustomPayload>()即可正常使用,不需要在枚举中硬编码所有类型,扩展性完全满足要求。
内容的提问来源于stack exchange,提问作者code-gorilla

