Project Icon

musli

Rust高性能灵活二进制序列化框架

Musli是一款为Rust开发的高性能二进制序列化框架。它提供多种序列化格式,支持#[no_std]和零拷贝序列化,可灵活编码同一Rust类型。Musli具有详细的错误追踪功能,适用于各种序列化场景。该框架在保持高性能的同时不牺牲功能完整性,是Rust生态系统中的一个强大工具。

musli

github crates.io docs.rs build status

卓越的性能,没有任何妥协1

Müsli 是一个灵活、快速且通用的 Rust 二进制序列化框架,与 [serde] 类似。

它提供了一系列格式,每种格式都有其明确记录的特性和权衡。包括 [musli::json] 在内的每种面向字节的序列化方法都完全支持 #[no_std],无论是否使用 alloc。还有一个特别出色的组件提供了简单明了的[零拷贝序列化][zerocopy]底层功能。


概览

  • 查看 [derives] 了解如何实现 [Encode] 和 [Decode]。
  • 查看 [data_model] 了解 Müsli 的抽象数据模型。
  • 查看[基准测试]和[大小比较]了解该框架的性能。
  • 查看 [tests] 了解该库的测试方法。
  • 查看 [musli::serde] 了解与 [serde] 的无缝兼容性。您可能还想了解 Müsli 与 serde 的区别

使用方法

Cargo.toml 中添加以下内容,使用您想要的格式

[dependencies]
musli = { version = "0.0.123", features = ["storage"] }

设计

主要工作由 [Encode] 和 [Decode] 派生宏完成,这些宏在 [derives] 模块中有详细文档。

Müsli 基于实现这些特征的类型所表示的模式进行操作。

use musli::{Encode, Decode};

#[derive(Encode, Decode)]
struct Person {
    /* .. 字段 .. */
}

注意 默认情况下,字段由其数字索引标识,如果重新排序字段,索引会发生变化。重命名字段和设置默认命名策略可以通过配置 [derives] 来完成。

提供的二进制序列化格式旨在高效且准确地编码 Rust 中可用的每种类型和数据结构。每种格式都有详细记录的权衡,并旨在使用时完全内存安全。

在内部,我们使用"编码"、"编码"和"解码"这些术语,因为它们与 [serde] 使用的"序列化"、"序列化"和"反序列化"不同,从而使两个库之间的互操作性更加清晰。编码和解码也更具有"二进制序列化"的感觉,这更贴近本框架的重点。

Müsli 的设计原则与 [serde] 类似。依靠 Rust 强大的特征系统来生成可以在很大程度上被优化掉的代码。最终结果应该与手写的高度优化代码非常相似。

例如,以下两个函数都生成相同的汇编代码(使用 --release 构建):

const OPTIONS: Options = options::new()
    .with_integer(Integer::Fixed)
    .with_byte_order(ByteOrder::NATIVE)
    .build();

const ENCODING: Encoding<OPTIONS> = Encoding::new().with_options();

#[derive(Encode, Decode)]
#[musli(packed)]
pub struct Storage {
    left: u32,
    right: u32,
}
使用musli的函数:

```rust
fn with_musli(storage: &Storage) -> Result<[u8; 8]> {
    let mut array = [0; 8];
    ENCODING.encode(&mut array[..], storage)?;
    Ok(array)
}

不使用musli的函数:

fn without_musli(storage: &Storage) -> Result<[u8; 8]> {
    let mut array = [0; 8];
    array[..4].copy_from_slice(&storage.left.to_ne_bytes());
    array[4..].copy_from_slice(&storage.right.to_ne_bytes());
    Ok(array)
}

Müsli 与 [serde] 的不同之处

Müsli 的数据模型不直接对应 Rust。没有提供被序列化类型元数据的 serialize_struct_variant 方法。[Encoder] 和 [Decoder] trait 对此是不可知的。与 Rust 类型的兼容性完全由 [Encode] 和 [Decode] 派生结合模式来处理。

我们使用 GATs 提供更易用的抽象。当 serde 设计时 GATs 还不可用。

所有东西都是 [Decoder] 或 [Encoder]。因此字段名不限于字符串或索引,而可以根据需要命名为[任意类型][musli-name-type]。

仅在需要时使用访问器serde 在反序列化时[完全使用访问器],相应的方法被视为底层格式的"提示"。然后反序列化器可以根据底层格式实际包含的内容自由调用访问器上的任何方法。在 Müsli 中,我们颠倒了这一点。如果调用者想要解码任意类型,它会调用 [decode_any]。然后格式可以发出适当的底层类型信号,或调用 [Visitor::visit_unknown] 告诉实现者它无法访问类型信息。

我们发明了模式编码 允许同一个 Rust 类型以多种不同方式编码,对编码方式有更大的控制。默认情况下我们包含 [Binary] 和 [Text] 模式,为二进制和基于文本的格式提供合理的默认值。

Müsli 从底层完全支持 [no-std 和 no-alloc],使用安全高效的[作用域分配]而不影响功能。

我们支持[详细跟踪] 解码时可以大大改善出错位置的诊断。

格式

当前格式通过支持不同程度的升级稳定性来区分。完全升级稳定的编码格式必须容忍一个模型可以添加旧版本模型应该能够忽略的字段。

部分升级稳定性仍然有用,就像下面的 [musli::storage] 格式,因为从存储读取只需要解码是升级稳定的。所以如果使用 #[musli(default)] 正确管理,这永远不会导致任何读取者看到未知字段。

可用格式及其功能如下:

reordermissingunknownself
[musli::storage] #[musli(packed)]
[musli::storage]
[musli::wire]
[musli::descriptive]
[musli::json] 2

reorder 决定字段是否必须按照在类型中指定的确切顺序出现。在这种类型中重新排序字段会导致某种未知但安全的行为。这只适用于每个客户端的数据模型严格同步的通信。

missing 决定读取是否可以通过类似 Option<T> 的方式处理缺失字段。这适用于磁盘存储,因为它意味着随着模式的演变可以添加新的可选字段。

unknown 决定格式是否可以跳过未知字段。这适用于网络通信。此时你已经达到了升级稳定性。这里可以进行一些级别的内省,因为序列化格式必须包含足够的字段信息来知道要跳过什么,这通常允许对基本类型进行推理。

self 决定格式是否是自描述的。允许从序列化状态完全重建数据结构。这些格式不需要模型来解码,可以与 [musli::value] 等动态容器相互转换以进行内省。这种格式还允许执行类型强制转换,因此如果符合目标类型,有符号数可以正确读取为无符号数。

每减少一个功能,格式就变得更紧凑高效。例如使用 #[musli(packed)] 的 [musli::storage] 大约与 [bincode] 一样紧凑,而 [musli::wire] 的大小与 [protobuf] 相当。所有格式主要面向字节,但如果好处明显,有些可能会执行[位压缩]。

升级稳定性

以下是使用 [musli::wire] 实现完全升级稳定性的示例。Version1 可以从 Version2 的实例中解码,因为它知道如何跳过属于 Version2 的字段。我们还明确地为字段添加 #[musli(name = ..)],以确保它们在重新排序时不会改变。

use musli::{Encode, Decode};

#[derive(Debug, PartialEq, Encode, Decode)]
struct Version1 {
    #[musli(mode = Binary, name = 0)]
    name: String,
}

#[derive(Debug, PartialEq, Encode, Decode)]
struct Version2 {
    #[musli(mode = Binary, name = 0)]
    name: String,
    #[musli(mode = Binary, name = 1)]
    #[musli(default)]
    age: Option<u32>,
}

let version2 = musli::wire::to_vec(&Version2 {
    name: String::from("Aristotle"),
    age: Some(61),
})?;

let version1: Version1 = musli::wire::decode(version2.as_slice())?;

以下是使用 [musli::storage] 对相同数据模型实现部分升级稳定性的示例。注意 Version2 如何从 Version1 解码,但不能反过来,这使其适用于磁盘存储,其中模式可以从旧版本演变到新版本。

let version2 = musli::storage::to_vec(&Version2 {
    name: String::from("Aristotle"),
    age: Some(61),
})?;

assert!(musli::storage::decode::<_, Version1>(version2.as_slice()).is_err());

let version1 = musli::storage::to_vec(&Version1 {
    name: String::from("Aristotle"),
})?;

let version2: Version2 = musli::storage::decode(version1.as_slice())?;

模式

与 [serde] 相比,在 Müsli 中同一个模型可以以不同的方式序列化。我们支持为单个模型实现不同的模式,而不是要求使用不同的模型。 模式是一种类型参数,它允许根据编码器配置的模式应用不同的属性。模式可以适用于任何musli属性,为您提供了很大的灵活性。

如果未指定模式,实现将应用于所有模式(M)。如果至少指定了一个模式,它将应用于模型中存在的所有模式和[Binary]。这样,使用默认模式Binary的编码应该始终有效。

有关如何配置模式的更多信息,请参阅[derives]。

以下是一个简单示例,展示如何使用两种模式为单个结构体提供两种完全不同的格式:

use musli::{Decode, Encode};
use musli::json::Encoding;

enum Alt {}

#[derive(Decode, Encode)]
#[musli(mode = Alt, packed)]
#[musli(name_all = "name")]
struct Word<'a> {
    text: &'a str,
    teineigo: bool,
}

const CONFIG: Encoding = Encoding::new();
const ALT_CONFIG: Encoding<Alt> = Encoding::new().with_mode();

let word = Word {
    text: "あります",
    teineigo: true,
};

let out = CONFIG.to_string(&word)?;
assert_eq!(out, r#"{"text":"あります","teineigo":true}"#);

let out = ALT_CONFIG.to_string(&word)?;
assert_eq!(out, r#"["あります",true]"#);

不安全性

以下是本crate中使用不安全代码的非详尽列表及其原因:

  • Tag::kind中的mem::transmute。它确保转换为#[repr(u8)]Kind枚举尽可能高效。

  • 一个主要不安全的SliceReader,它提供比&[u8]的默认Reader实现更高效的读取。因为它可以直接在指针上执行大部分必要的比较。

  • musli::json中与UTF-8处理相关的一些不安全性,因为我们内部自己检查UTF-8有效性(类似serde_json)。

  • FixedBytes<N>是一个可以操作未初始化数据的基于栈的容器。它的实现大部分是不安全的。通过它可以执行基于栈的序列化,这在no-std环境中很有用。

  • 在所有二进制格式中,一些unsafe用于拥有所有权的String解码,以支持通过[simdutf8]进行更快的字符串处理。禁用simdutf8功能(默认启用)会移除这些不安全使用。

为确保这个库在内存安全方面的正确实现,使用miri进行了广泛的测试和模糊测试。更多信息请参见[tests]。


[此处省略了原文中的链接引用部分]

Footnotes

  1. 意味着 Müsli 应该能够满足您的所有需求,甚至更多。

  2. 这严格来说不是二进制序列化,但它是作为一个试金石来实现的,以确保 Müsli 具有支持它所需的必要框架功能。幸运的是,这个实现也相当不错!

项目侧边栏1项目侧边栏2
推荐项目
Project Cover

豆包MarsCode

豆包 MarsCode 是一款革命性的编程助手,通过AI技术提供代码补全、单测生成、代码解释和智能问答等功能,支持100+编程语言,与主流编辑器无缝集成,显著提升开发效率和代码质量。

Project Cover

AI写歌

Suno AI是一个革命性的AI音乐创作平台,能在短短30秒内帮助用户创作出一首完整的歌曲。无论是寻找创作灵感还是需要快速制作音乐,Suno AI都是音乐爱好者和专业人士的理想选择。

Project Cover

有言AI

有言平台提供一站式AIGC视频创作解决方案,通过智能技术简化视频制作流程。无论是企业宣传还是个人分享,有言都能帮助用户快速、轻松地制作出专业级别的视频内容。

Project Cover

Kimi

Kimi AI助手提供多语言对话支持,能够阅读和理解用户上传的文件内容,解析网页信息,并结合搜索结果为用户提供详尽的答案。无论是日常咨询还是专业问题,Kimi都能以友好、专业的方式提供帮助。

Project Cover

阿里绘蛙

绘蛙是阿里巴巴集团推出的革命性AI电商营销平台。利用尖端人工智能技术,为商家提供一键生成商品图和营销文案的服务,显著提升内容创作效率和营销效果。适用于淘宝、天猫等电商平台,让商品第一时间被种草。

Project Cover

吐司

探索Tensor.Art平台的独特AI模型,免费访问各种图像生成与AI训练工具,从Stable Diffusion等基础模型开始,轻松实现创新图像生成。体验前沿的AI技术,推动个人和企业的创新发展。

Project Cover

SubCat字幕猫

SubCat字幕猫APP是一款创新的视频播放器,它将改变您观看视频的方式!SubCat结合了先进的人工智能技术,为您提供即时视频字幕翻译,无论是本地视频还是网络流媒体,让您轻松享受各种语言的内容。

Project Cover

美间AI

美间AI创意设计平台,利用前沿AI技术,为设计师和营销人员提供一站式设计解决方案。从智能海报到3D效果图,再到文案生成,美间让创意设计更简单、更高效。

Project Cover

AIWritePaper论文写作

AIWritePaper论文写作是一站式AI论文写作辅助工具,简化了选题、文献检索至论文撰写的整个过程。通过简单设定,平台可快速生成高质量论文大纲和全文,配合图表、参考文献等一应俱全,同时提供开题报告和答辩PPT等增值服务,保障数据安全,有效提升写作效率和论文质量。

投诉举报邮箱: service@vectorlightyear.com
@2024 懂AI·鲁ICP备2024100362号-6·鲁公网安备37021002001498号