4个版本

0.2.1 2021年7月13日
0.2.0 2021年7月5日
0.1.2 2021年6月29日
0.1.0 2021年6月26日

#1135 in 数据结构

35 每月下载量
2 crates 中使用

Apache-2.0

33KB
419

aversion

Rust中的版本化数据结构

此crate仍在开发中。

此crate的目标是使版本化数据结构变得简单。例如,假设我们从以下结构体开始

struct FooV1 {
    val: u32,
}

如果我们以这种格式将数据序列化到文件中,但后来发现我们想要进行更改

struct FooV2 {
    val: u64,
}

...那么如果我们想要支持我们之前的文件,我们就有很多工作要做。我们可能需要在文件头中增加一个版本号,并且可能需要通过所有使用 FooV1 的不同位置,并决定是否将其升级到 FooV2。任何使用 FooV1 的地方,我们都必须保留该代码,否则可能会破坏兼容性。

此crate添加了允许我们跟踪每个结构体的版本的特质,并派生出特质和方法,允许任何结构体动态升级到最新版本。这意味着大多数代码只需与最新版本交互,同时仍然能够读取旧文件。

为了使这成为可能,结构体必须遵循特定的模式

  • 版本化结构体必须遵循命名约定 Name + V + {integer},即 FooV1BarV42
  • 版本必须从1开始,并且是连续的。
  • 必须有一个类型别名 type Foo = FooV3 指向最新版本。
  • 对于每一对版本,NN+1,必须实现特质 FromVersion。例如

impl FromVersion<FooV1> for FooV2 {
    fn from_version(v1: FooV1) -> Self {
        FooV2 { val: v1.val.into() }
    }
}

此crate仍较新,这些规则可能在将来发生变化。

反序列化

我们希望能够在不知道存储版本的情况下反序列化数据结构。

为此,我们使用 DataSource 特性,以指定我们的序列化格式、头部格式和错误类型。

一旦实现了 UpgradeLatest 特性(为此有一个 derive 宏),我们就可以快速反序列化任何版本的数据结构,例如:

// Define a data source
let src = CborData::new(...);
// Read a the next header + message, and upgrade to the latest version
let msg: Foo = data_src.expect_message()?;

请注意,在这个例子中,msg 总是 Foo 结构体的最新版本,无论实际存储的是哪个版本。只要 FromVersion 代码正确,程序的其他部分就无需知道从文件中读取的是哪个版本。

消息组

我们可以将此逻辑扩展到不同消息的组,以自动构建一个分发函数。例如,如果我们定义了一组消息:

enum MyProtocol {
    Foo(Foo),
    Bar(Bar),
}

我们可以派生 GroupDeserialize 特性,它可以自动反序列化 MyProtocol 中的任何消息

let incoming_message = MyProtocol::read_message(&mut my_data_source)?;
match incoming_message {
    Foo(f) => {
        // handle the received Foo message
    }
    Bar(b) => {
        // handle the received Bar message
    }
}

与上一个例子类似,头部会告诉我们发送了哪个消息(即 FooBar),以及该结构的版本(FooV1FooV2),而 read_message 会反序列化结构的正确版本,将其升级到最新版本,并以 MyProtocol 枚举的形式返回,供调用者处理。

许可:Apache-2.0

依赖项

~0.9–1.5MB
~34K SLoC