#serialization #formula #deserialize #macro-derive #derive-deserialize #slice #zero-copy

无需std alkahest

出色的序列化库,具有零开销序列化和零复制反序列化功能

16个不稳定版本 (3个破坏性更新)

0.3.0 2023年5月2日
0.2.0 2023年4月3日
0.2.0-rc.82023年3月30日
0.2.0-rc.22023年2月21日
0.0.0 2021年7月17日

#361编码

Download history 66/week @ 2024-03-11 58/week @ 2024-03-18 55/week @ 2024-03-25 60/week @ 2024-04-01 42/week @ 2024-04-08 22/week @ 2024-04-15 25/week @ 2024-04-22 27/week @ 2024-04-29 10/week @ 2024-05-06 10/week @ 2024-05-13 19/week @ 2024-05-20 16/week @ 2024-05-27 29/week @ 2024-06-03 86/week @ 2024-06-10 74/week @ 2024-06-17 55/week @ 2024-06-24

每月245次 下载
用于 4 crates

MIT/Apache

200KB
5K SLoC

Alkahest - 优秀的序列化库。

crates docs actions MIT/Apache loc

Alkahest 是一个闪电般快速、零依赖、零开销、零不安全的基于模式的序列化库。它适用于广泛的用例,但针对自定义高性能网络协议进行了优化。

基准测试

此基准测试模拟了一些游戏网络协议。

alkahest bincode rkyv speedy
serialize 10.69 us (✅ 1.00x) 11.08 us (✅ 1.04x slower) 12.43 us (❌ 1.16x slower) 11.13 us (✅ 1.04x slower)
read 1.19 us (✅ 1.00x) 9.19 us (❌ 7.74x slower) 2.10 us (❌ 1.77x slower) 1.54 us (❌ 1.30x slower)

criterion-table 制作

另请参阅基准测试结果,来自https://github.com/djkoloski/rust_serialization_benchmark (0.2版本发布前处于草案状态)。

特性

  • 基于模式的序列化。Alkahest使用名为 Formula 的数据模式来序列化和反序列化数据。因此可以独立于序列化或反序列化的数据类型来控制数据布局。

  • 支持多种公式的广泛类型。整数、浮点数、布尔值、元组、数组、切片、字符串以及使用derive宏定义的用户自定义公式,这些公式可以具有自定义数据布局。该宏适用于任何复杂性的结构体和枚举,并支持泛型。

  • 序列化序列无开销。Alkahest支持直接将迭代器序列化为切片公式。无需再分配一个Vec来序列化,然后立即丢弃。

  • 惰性反序列化。Alkahest提供了Lazy<F>类型,可以惰性地反序列化任何公式F。之后可以使用Lazy来执行实际的反序列化。
    Lazy<[F]>还可以产生一个迭代器,它按需反序列化元素。
    惰性在类型级别上进行控制,可以应用于更大公式的任何元素。

  • 不可错序列化。只要缓冲区足够大或正在增长,任何实现了Serialize的值都可以无错误地序列化。不再需要不必要的展开或解决“如果序列化失败怎么办?”的难题。序列化的唯一错误条件是“数据不匹配”。

计划中的功能

  • 可序列化公式描述符
  • 兼容性规则
  • 用于C和Rust的公式描述符代码生成的外部工具。

它的工作原理。更详细的说明

Alkahest将数据模式定义(即Formula)与序列化和反序列化代码分开。这样做,该库为可序列化数据类型和反序列化数据类型不同的情况提供了更好的保证。它还支持从迭代器而不是集合中进行序列化,并将反序列化到延迟包装器中,该包装器延迟了昂贵的操作,并且如果值从未被访问,则可能完全省略它。用户通过选择合适的Deserialize实现来在类型级别上控制惰性。例如,反序列化到Vec<T>是急切的,因为Vec<T>是用所有T实例构建的,并为它们分配了内存。而alkahest::SliceIter实现了Iterator,并在Iterator::next和其他方法中反序列化元素。并且提供对任何元素的常数时间随机访问。

灵活性是以使用仅字节切片进行序列化和反序列化为代价的。并且序列化数据比其他某些二进制格式更大。

关于支持密集数据打包的问题仍然是开放的。可能希望在类型级别上进行控制。

错误和恐慌

API的设计遵循以下原则:如果缓冲区足够大,则任何值都可以成功序列化。数据不能引起恐慌,但错误的特质实现可以。

在库中没有任何不安全的代码,它生成的任何代码都没有。如果没有std不安全,则不可能有UB。

向前和向后兼容性

没有数据模式会保持不变。新的字段和变体被添加,而其他的则被弃用和删除。

有一套确保公式之间向前兼容性的规则,还有另一套用于向后兼容性的规则。

尚未实现兼容性的验证。

向前兼容性

向前兼容性是能够反序列化使用较新公式序列化的数据的能力。

TODO:列出所有规则

向后兼容性

向后兼容性是能够反序列化使用较旧公式序列化的数据的能力。

TODO:列出所有规则

公式、Serialize和Deserialize特质。

该库通过三个基本特性工作。分别是 FormulaSerializeDeserialize。还有一个支持性特性 BareFormula

Alkahest 提供了进程宏 alkahest 用于推导 FormulaSerializeDeserialize

Formula

Formula 特性用于允许类型作为数据模式。使用给定公式序列化的任何值都应该可以用相同的公式反序列化。仅共享 Formula 类型就允许模块和库轻松通信。Formula 规定了二进制数据布局,它必须是平台无关的。

理论上,可以从单独的文件生成 Formula 类型,从而开辟了跨语言通信的可能性。

Formula 为许多类型提供了默认实现。例如,原始类型如 bool、整数和浮点类型都实现了 Formula。这不包括 isizeusize。取而代之的是,提供了 FixedUsizeFixedIsize 类型,其大小由功能标志控制。注意! 大小和地址以 FixedUsize 序列化。如果 usize 值过大,则会截断。这可能会导致生成损坏的数据并在调试时引发恐慌。如果您遇到这种情况,请增加 FixedUsize 的大小。它还实现了元组、数组、切片、OptionVec(后者需要 "alloc" 功能)。

定义新公式的最简单方法是为一组或枚举推导 Formula 特性。支持泛型,但可能需要为 SerializeDeserialize 推导宏指定复杂的界限。唯一的约束是所有字段都必须实现 Formula

Serialize

Serialize<Formula> 特性用于根据特定公式实现序列化。序列化写入可变字节切片,并且不应执行动态分配。任何类型序列化的二进制结果都必须遵循它。最后,如果序列化的原始数据流相同,则二进制结果也应相同。类型可能具有不同的公式,产生不同的二进制结果。

Serialize 为许多类型提供了实现。最值得注意的是,所有原始类型 T(除 usizeisize 外)都有 T: Serialize<T>&T: Serialize<T> 的实现。另一个重要的实现是 Serialize<F> for I where I: IntoIterator, I::Item: Serialize<F>,允许从迭代器和集合直接序列化到切片。使用公式 Ref<F> 的序列化使用公式 F 进行序列化,然后存储相对地址和大小。无需动态分配。

对于一个类型的 Serialize 提取将生成 Serialize 实现代码,公式在属性 #[alkahest(FormulaRef)]#[alkahest(serialize(FormulaRef))] 中指定。通常,FormulaRef 是一个类型。当使用泛型时,它还包括泛型参数和边界。如果没有指定公式,则假定 Self。同样需要为该类型推导出 Formula。不建议为具有手动 Formula 实现的公式推导出 Serialize,因为 Serialize 推导宏生成的代码使用了由 Formula 推导宏生成的非公开项。因此,两者都应该有手动实现或都进行推导。

对于结构体,Serialize 推导宏要求所有字段都存在于 SerializeFormula 结构中,并且顺序相同(如果是同一结构体,则非常简单)。

对于枚举,Serialize 推导宏会检查对于每个变体,在 Formula 枚举中是否存在相应的变体。变体内容与结构体类似进行比较。序列化会插入变体 ID 并将变体作为结构体进行序列化。变体的大小可能不同。如有必要,外层值序列化会插入填充。

如果 Formula 是枚举,可以为结构体推导出 Serialize。在这种情况下,应该使用 #[alkahest(@variant_ident)]#[alkahest(serialize(@variant_ident))] 进行指定,然后 Serialize 推导宏将生成序列化代码,它就像这个变体是一个结构体 Formula 一样工作,只是变体的 ID 会先于字段序列化。

只有当 Formula 也是枚举时,才能为枚举推导出 Serialize。可序列化的枚举可以省略 Formula 中的某些(或所有)变体。它不能缺少 Formula 中的变体。每个变体随后遵循结构体的规则。

为了方便起见,Infallible 为枚举公式实现了 Serialize

反序列化

Deserialize<'de, Formula> trait 用于根据特定公式实现反序列化。反序列化从字节数组读取并构建反序列化值。反序列化 不应该 执行除了构建和初始化反序列化值所需的动态分配以外的任何动态分配。例如,如果反序列化出非零数量的 T 值时,可以分配 Vec<T>,但它 不应该 过度分配。

类似于 Serializealkahest 提供了多个现成的 Deserialize trait 实现。可以使用原始公式 T 反序列化 From<T> 类型。

可以使用公式 F 反序列化的值也可以使用 Ref<F> 进行反序列化,它读取地址和长度,然后继续使用公式 F

Vec<T> 可以使用切片公式进行反序列化。为实现了 Iterator 并懒加载反序列化 T: Deserialize<'de, F> 类型元素的 alkahest::SliceIter<'de, T> 类型实现了 Deserialize<'de, [F]>SliceIter 是可克隆的,可以从两端迭代,并且可以在常数时间内跳过元素。为了方便起见,SliceIter 还可以与数组公式进行反序列化。

为类型推导 Deserialize 将生成 Deserialize 实现,公式在属性 #[alkahest(FormulaRef)]#[alkahest(deserialize(FormulaRef))] 中指定。通常,FormulaRef 是一个类型。当使用泛型时,它还包含泛型参数和界限。如果未指定公式,则假定 Self。还应该为类型推导 Formula。不建议为具有手动 Formula 实现的公式推导 Deserialize,因为 Deserialize 推导宏生成的代码使用由 Formula 推导宏生成的非公共项。因此,两者都应该有手动实现或都进行推导。

serde 的互操作性

Alkahest 非常酷,但几乎到处都在使用 serde,这也有很好的理由。在设计 Formula 时,可能希望包含支持序列化的现有类型 serde,尤其是如果它来自另一个包。这个包提供了 BincodeBincoded<T> 公式来覆盖这一点。任何实现了 serde::Serialize 的东西都可以使用 Bincode 公式进行序列化,当然它将使用 bincode 包进行序列化。Bincoded<T>Bincode 的一个受限版本,只适用于 T

使用示例

// This requires two default features - "alloc" and "derive".
#[cfg(all(feature = "derive", feature = "alloc"))]
fn main() {
  use alkahest::{alkahest, serialize_to_vec, deserialize};

  // Define simple formula. Make it self-serializable.
  #[derive(Clone, Debug, PartialEq, Eq)]
  #[alkahest(Formula, SerializeRef, Deserialize)]
  struct MyDataType {
    a: u32,
    b: Vec<u8>,
  }

  // Prepare data to serialize.
  let value = MyDataType {
    a: 1,
    b: vec![2, 3],
  };

  // Use infallible serialization to `Vec`.
  let mut data = Vec::new();

  // Note that this value can be serialized by reference.
  // This is default behavior for `Serialized` derive macro.
  // Some types required ownership transfer for serialization.
  // Notable example is iterators.
  let (size, _) = serialize_to_vec::<MyDataType, _>(&value, &mut data);

  let de = deserialize::<MyDataType, MyDataType>(&data[..size]).unwrap();
  assert_eq!(de, value);
}

#[cfg(not(all(feature = "derive", feature = "alloc")))]
fn main() {}

基准测试

Alkahest 包含了一个基准测试来与其他流行的序列化包进行比较。只需运行 cargo bench --all-features 就可以看到结果。

许可证

许可方式为以下之一

由您选择。

贡献

除非您明确声明,否则根据 Apache-2.0 许可证定义,您提交的任何有意包含在作品中的贡献,都应如上所述双重许可,而不附加任何其他条款或条件。

依赖项

~250KB