29个版本

0.2.18 2024年8月11日
0.2.17 2024年7月28日
0.2.16 2024年2月26日
0.2.14 2023年12月10日
0.1.1 2019年1月24日

#136 in 编码

Download history 78694/week @ 2024-05-03 85544/week @ 2024-05-10 90318/week @ 2024-05-17 80262/week @ 2024-05-24 76464/week @ 2024-05-31 79782/week @ 2024-06-07 77964/week @ 2024-06-14 80048/week @ 2024-06-21 76768/week @ 2024-06-28 76055/week @ 2024-07-05 79050/week @ 2024-07-12 78088/week @ 2024-07-19 74477/week @ 2024-07-26 81095/week @ 2024-08-02 87505/week @ 2024-08-09 59836/week @ 2024-08-16

318,294 monthly downloads
用于 364 个crate (147直接)

MIT/Apache

125KB
3.5K SLoC

Typetag

github crates.io docs.rs build status

Serde序列化和反序列化trait对象。

此crate提供了一个宏,用于轻松序列化&dyn Trait trait对象以及序列化和反序列化Box<dyn Trait> trait对象。

让我们看看示例,下面我会做更多解释。

[dependencies]
typetag = "0.2"

支持rustc 1.62+


示例

假设我有一个trait WebEvent,并且要求该trait的所有实现都是可序列化和可反序列化的,以便我可以将它们发送到我的广告服务AI。以下是我们开始所需的类型和trait实现

trait WebEvent {
    fn inspect(&self);
}

#[derive(Serialize, Deserialize)]
struct PageLoad;

impl WebEvent for PageLoad {
    fn inspect(&self) {
        println!("200 milliseconds or bust");
    }
}

#[derive(Serialize, Deserialize)]
struct Click {
    x: i32,
    y: i32,
}

impl WebEvent for Click {
    fn inspect(&self) {
        println!("negative space between the ads: x={} y={}", self.x, self.y);
    }
}

我们需要能够将任意Web事件作为JSON发送到AI

fn send_event_to_money_factory(event: &dyn WebEvent) -> Result<()> {
    let json = serde_json::to_string(event)?;
    somehow_send_json(json)?;
    Ok(())
}

并在服务器端接收任意Web事件作为JSON

fn process_event_from_clickfarm(json: &str) -> Result<()> {
    let event: Box<dyn WebEvent> = serde_json::from_str(json)?;
    overanalyze(event)?;
    Ok(())
}

介绍称这将是无缝的,但我会让你来判断。

首先在trait顶部添加一个属性。

#[typetag::serde(tag = "type")]
trait WebEvent {
    fn inspect(&self);
}

然后在每个impl块上也添加一个类似的属性。

#[typetag::serde]
impl WebEvent for PageLoad {
    fn inspect(&self) {
        println!("200 milliseconds or bust");
    }
}

#[typetag::serde]
impl WebEvent for Click {
    fn inspect(&self) {
        println!("negative space between the ads: x={} y={}", self.x, self.y);
    }
}

现在它按描述工作。总共添加了三行!


什么?

这个库将trait对象序列化,就像Serde枚举一样。程序中任何地方的trait实现都看起来像枚举的一个变体。

Serde的所有三种标记枚举表示都得到了支持。上面显示的是“内部标记”样式,因此我们的两种事件类型在JSON中表示为

{"type":"PageLoad"}
{"type":"Click","x":10,"y":10}

枚举表示的选择由加在trait定义上的属性控制。让我们看看“相邻标记”样式

#[typetag::serde(tag = "type", content = "value")]
trait WebEvent {
    fn inspect(&self);
}
{"type":"PageLoad","value":null}
{"type":"Click","value":{"x":10,"y":10}}

和“外部标记”样式,这是Serde枚举的默认样式

#[typetag::serde]
trait WebEvent {
    fn inspect(&self);
}
{"PageLoad":null}
{"Click":{"x":10,"y":10}}

此外,给定特质的 impl 的标签值可以定义在特质 impl 的属性中。默认情况下,如果没有明确指定名称,则标签将是类型名称。

#[typetag::serde(name = "mouse_button_down")]
impl WebEvent for Click {
    fn inspect(&self) {
        println!("negative space between the ads: ({}, {})", self.x, self.y);
    }
}
{"type":"mouse_button_down","x":10,"y":10}

从概念上讲,这个 crate 为你构建了一个枚举,其中程序中每个特质的 impl 都自动注册为枚举变体。其行为与你自己编写枚举并实现 Serialize 和 Deserialize 为 dyn Trait 对象的方式相同。

// generated (conceptually)
#[derive(Serialize, Deserialize)]
enum WebEvent {
    PageLoad(PageLoad),
    Click(Click),
    /* ... */
}

有这么多问题

  • 如果特质 impl 分散在不同的 crate 中,它是否还能工作? 是的

    序列化和反序列化支持最终程序二进制依赖图中的每个特质的 impl。

  • 在 Bincode 这样的非自我描述数据格式中它是否工作? 是的

    所有三种枚举表示方法都可以正确地通过紧凑的二进制格式(包括 Bincode)进行往返。

  • 它支持非结构类型吗? 是的

    特质的实现可以是结构体、枚举、基本类型或其他 Serde 支持的任何类型。Serialize 和 Deserialize 实现可以是推导出来的或手动编写的。

  • 有没有人向我解释过为什么这不可能实现? 是的

    可能是我。

  • 那么它是如何工作的呢?

    我们使用 inventory crate 来生成你的特质的 impl 的注册表,它基于 ctor crate 来连接初始化函数,这些函数将插入到注册表中。第一次 Box<dyn Trait> 反序列化将执行迭代注册表并构建标签到反序列化函数的映射的工作。随后的反序列化将在此映射中找到正确的反序列化函数。erased-serde crate 也参与了整个过程,以确保不破坏对象安全性。


许可证

根据您的选择,受Apache License, Version 2.0MIT 许可证的许可。
除非您明确声明,否则您根据 Apache-2.0 许可证定义的任何贡献,都将根据上述方式双重许可,不附加任何额外的条款或条件。

依赖关系

~0.6–1.2MB
~26K SLoC