#serialization #serde #trait-object #dyn #value-object

serde_flexitos

使用Serde对特质对象进行灵活的序列化和反序列化

3个不稳定版本

0.2.1 2024年3月16日
0.2.0 2024年2月3日
0.1.0 2023年10月26日

#402 in 编码

Download history 30/week @ 2024-03-28 13/week @ 2024-04-04 16/week @ 2024-04-11 33/week @ 2024-04-18 25/week @ 2024-04-25 18/week @ 2024-05-02 15/week @ 2024-05-16 22/week @ 2024-05-23 34/week @ 2024-05-30 29/week @ 2024-06-06 43/week @ 2024-06-13 25/week @ 2024-06-20 58/week @ 2024-06-27 73/week @ 2024-07-04 103/week @ 2024-07-11

261 每月下载量

Apache-2.0

43KB
403

Serde FlexiTOS

crates.io docs GitHub License

这个crate提供了对特质对象进行灵活序列化和反序列化的类型和函数。

为什么需要它?

当你需要将实现某个特质的多种类型 视为单一类型 时,在Rust中,特质对象类型 dyn O 是最方便的解决方案之一。如果你需要对这样的特质对象进行(反)序列化,这个crate可以帮助你实现。

这已经不可能了吗?

使用 erased-serdeerased_serde::Serialize 已经可以实现特质对象的序列化。然而,erased-serde 并没有提供方便的方式来 反序列化特质对象

要反序列化特质对象,我们首先需要确定被序列化的具体类型,然后使用该类型的相应 Deserialize 实现来反序列化值。我们不能直接使用特质对象类型来获取相应的 Deserialize 实现,因为特质对象必须是对象安全的,这意味着不允许关联函数(只允许方法:接受 &self 及其变体的函数)。但在反序列化时,我们没有特质对象的实例(我们是在反序列化时实例化它的!),因此没有方法可以调用。因此,需要一个外部机制来获取具体类型的 Deserialize 实现。

据我所知,还有两种其他解决方案,但它们做出了权衡,并不是每个人都愿意接受,而且没有提供退出这些权衡的方法。

  • typetag:一种方便的解决方案,用于为特质对象获取(反)序列化功能。然而,它使用 inventory 来注册 Deserialize 实现,这并不适用于所有平台(例如,WASM)。它还使用一个必须应用于每个具体类型的过程宏来全局注册这些实现。最后,泛型特质和泛型实现的特质不受支持。如果您可以在这个限制范围内工作,typetag 是一个很好的crate,您可能应该用它来代替这个crate,因为它更方便!
  • serde_traitobject:一个有趣的解决方案,可以(反)序列化特质对象的整个vtable。然而,vtable在不同编译之间是不稳定的,当您添加或删除特质对象的实现时会发生改变,并且需要nightly Rust。因此,这个crate只能在非常有限的范围内使用,即在相同二进制文件的不同进程之间发送特质对象,但对于这个特定的用例来说非常方便。

这个crate提供了用于灵活(反)序列化特质对象的数据类型和函数,不必要使用宏、全局注册、nightly Rust,也不需要您的二进制文件不改变,以牺牲一些便利性为代价。然而,通过在这个crate之上创建层,例如全局注册宏,您可以自己决定便利性和灵活性之间的权衡。

它是如何工作的?

一个特质对象以一个id-value对的形式进行序列化,也称为externally tagged enum representation,其中id是该值的具体类型的唯一标识符,而值使用特质对象的 erased_serde::Serialize 实现进行序列化。

通过首先反序列化ID,然后使用该ID查找具体类型的 Deserialize 实现,最后使用该反序列化实现来反序列化值,从而反序列化一个特质对象。

一个ID必须唯一标识特质对象的具体类型,并且在时间上保持稳定,以便在时间上保持反序列化的正常工作。缺失或重复的ID将导致反序列化期间出现(可恢复)错误。

我如何使用这个crate?

注册表处理 Deserialize 实现的注册以及通过ID查找它们。对于您希望反序列化的每个特质对象,您必须构建一个注册表,并将所有具体类型注册到其中。目前,MapRegistry 是唯一的注册表实现。

要注册一个具体类型,我们必须提供以下信息

  1. 该具体类型的ID(&'static str),
  2. 一个反序列化函数,它将具体类型反序列化为一个boxed特质对象。

特质必须有一个 erased_serde::Serialize 作为超特质,并且有一个方法可以检索具体类型的ID。特质的具体类型必须实现 Serialize

然后,您可以使用 serialize_trait_objectdyn Trait 实现 Serialize,并使用 deserialize_trait_object(Registry::deserialize_trait_object)为 Box<dyn Trait> 实现 Deserialize

示例

示例,使用全局注册表以获得一些便利性

use once_cell::sync::Lazy;
use serde::{Deserialize, Deserializer, Serialize, Serializer};

use serde_flexitos::{MapRegistry, Registry, serialize_trait_object};

// Trait we want to serialize trait objects of. This example just uses `Debug` as supertrait so we can print values.

pub trait Example: erased_serde::Serialize + std::fmt::Debug {
  // Gets the ID that uniquely identifies the concrete type of this value. Must be a method for object safety.
  fn id(&self) -> &'static str;
}

// Implementations of the `Example` trait.

#[derive(Clone, Serialize, Deserialize, Debug)]
struct Foo(String);
impl Foo {
  const ID: &'static str = "Foo";
}
impl Example for Foo {
  fn id(&self) -> &'static str { Self::ID }
}

#[derive(Clone, Serialize, Deserialize, Debug)]
struct Bar(usize);
impl Bar {
  const ID: &'static str = "Bar";
}
impl Example for Bar {
  fn id(&self) -> &'static str { Self::ID }
}

// Create registry for `Example` and register all concrete types with it. Store in static with `Lazy` to lazily
// initialize it once while being able to create global references to it.

static EXAMPLE_REGISTRY: Lazy<MapRegistry<dyn Example>> = Lazy::new(|| {
  let mut registry = MapRegistry::<dyn Example>::new("Example");
  registry.register(Foo::ID, |d| Ok(Box::new(erased_serde::deserialize::<Foo>(d)?)));
  registry.register(Bar::ID, |d| Ok(Box::new(erased_serde::deserialize::<Bar>(d)?)));
  registry
});

// (De)serialize implementations

impl Serialize for dyn Example {
  fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
    serialize_trait_object(serializer, self.id(), self)
  }
}

impl<'de> Deserialize<'de> for Box<dyn Example> {
  fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
    EXAMPLE_REGISTRY.deserialize_trait_object(deserializer)
  }
}

// Run serialization roundtrip

fn main() -> Result<(), Box<dyn std::error::Error>> {
  let examples: Vec<Box<dyn Example>> = vec![Box::new(Foo("A".to_string())), Box::new(Bar(0))];
  println!("Examples: {:?}", examples);
  let json = serde_json::to_string(&examples)?;
  println!("Serialized: {}", json);
  let roundtrip: Vec<Box<dyn Example>> = serde_json::from_str(&json)?;
  println!("Deserialized: {:?}", roundtrip);
  Ok(())
}

查看示例以获取更多用例

  • example/simple.rs:上面示例的完整版本。
  • example/macros.rs:在crate之上层叠便利性宏,使用 linkme 来注册类型。
  • example/no_global.rs:使用本地注册表而不是全局注册表,使用本仓库提供的DeserializeSeed实现。
  • example/generic_instantiations.rs:创建并使用泛型特质/结构的实例化注册表。但是并不泛型处理特质和结构!

限制

泛型序列化和反序列化

不支持泛型类型参数的特质对象和泛型类型参数的结构体的序列化和反序列化,我认为这是不可能支持的。要支持这一点,我们需要一个从运行时唯一标识符(&str)到编译时类型的函数,但这是不可能的。这是有道理的,因为编译器需要在编译时知道使用了哪些(组合的)具体类型作为泛型类型参数来进行单态化。

然而,你可以注册所有你希望反序列化的类型的具体实例,就像在example/generic_instantiations.rs中所做的那样。

其他表示

仅支持(反)序列化特质对象的外部标记枚举表示(externally tagged enum representation),以简化本仓库的实现。这只会成为问题,如果你需要接受使用不同表示(即不是本仓库)序列化的序列化特质对象。

灵感来源

本仓库受优秀的typetag仓库的启发。

依赖项

~260–495KB
~11K SLoC