#序列化 #数据 #变量 #静态 #数据源 #构建 #嵌入

构建 uneval_static

Serde序列化器,将数据嵌入为Rust代码

4个版本

0.1.2 2024年5月18日
0.1.1 2024年5月17日
0.1.0 2024年5月17日
0.0.1 2024年5月17日

#111 in 构建工具

Download history 119/week @ 2024-05-11 175/week @ 2024-05-18 7/week @ 2024-05-25

98 每月下载量
用于 poestat_static

MIT 许可证

27KB
426

uneval_static

将数据序列化到可以作为静态变量存储的Rust源代码中

如何实现?

build.rs 中生成数据

#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
struct Unit(());

#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
struct Input {
    string: String,
    vector: Vec<i32>,
    reference: Option<Box<Input>>,
    map: HashMap<String, Unit>,
}

fn main() {
    let input = Input::deserialize(json!({
        "string": "test",
        "vector": [1,2,3],
        "reference": {"string": "", "vector": [], "map": {}},
        "map": {"key": null},
    }))
    .unwrap();

    let path = Path::new(&env::var("OUT_DIR").unwrap()).join("generated.rs");
    let mut uneval = uneval_static::ser::Uneval::new(File::create(path).unwrap());
    // Mappings may be required depending on your data structure
    // Serde provides type names and field names as plain identifiers;
    // these can be mapped to any output text
    uneval.add_mapping("Input".into(), "&Output".into());
    uneval.add_mapping("vector".into(), "slice".into());
    uneval.serialize(input).unwrap();
}

然后在 src/some_mod.rs 中包含生成的代码

#![allow(clippy::all)]

pub struct Unit(());

pub struct Output {
    string: &'static str,
    slice: &'static [i32],
    reference: Option<&'static Output>,
    map: phf::Map<&'static str, Unit>,
}

pub static VALUE: &Output = include!(concat!(env!("OUT_DIR"), "/generated.rs"));

为什么这么做?

从库中分叉出的这个crate,uneval,通过使用如.into()之类的trait函数来将serde类型转换为Rust类型,从而提供类型灵活性。然而,这意味着输出代码必须承担一些运行时成本来初始化自身,这在情感层面上以及性能层面上都是次优的。这个crate输出的代码可以适应更窄的类型范围,但不需要lazy_static初始化器。

它是如何工作的?

有关详细信息,请参阅crate文档。简而言之,我们使用Serde提供的信息来生成代码,当将其分配给正确类型的变量时,将使用Into和迭代器提供所有必要的转换。

这真的那么简单吗?

嗯...并不简单。存在一些限制。

  1. 序列化结构体中使用的所有类型必须在包含位置的作用域内。Serde不提供序列化器的限定名称(即路径),只提供“最后一个”名称。可能最简单的方法是将序列化数据用作以下
let static VALUE: MainType = {
    use ::path::to::Type1;
    // ...and other types
    include!("path/to/file.rs")
}
  1. 因此,序列化器使用的所有类型都必须具有不同的名称(否则它们将相互冲突)。
  2. 没有实现反序列化器。这是故意的,因为这个crate并不真正打算用于运行时使用。实际上,反序列化器已经实现了 - 它实际上是Rust编译器本身。
  3. 本序列化器旨在与派生实现一起使用。当与定制的 Serialize 一起使用时,可能会返回错误的结果。

如果您发现其他任何不工作的情况,请随时提交一个 pull request。

测试

此 crate 使用 trybuild 来运行其测试。每个测试案例都会输出到 test_fixtures/ 目录下的单独目录,其中 {test_name}/main.rs 首先被编译和运行,从而创建 {test_name}/generated.rs,用于编译 {test_name}/user.rs,断言生成的值与输入匹配。

测试数据定义在 test_fixtures/data.toml 中,格式如下

  • TOML 中的部分名称对应于测试案例的名称。请注意,只有一个测试由 cargo 运行,每个测试案例由 cargo test 在多个阶段生成、编译和运行。
  • 字段 main_type 对应于正在测试的序列化类型。
  • 字段 definition 是类型定义。它将原样包含在 {test_name}/main.rs 中,并在将其转换为静态声明后包含在 {test_name}/user.rs 中。由于这些特质在测试入口运行期间使用,因此需要在这些类型上实现 DebugSerializePartialEq
  • 字段 value 实际上在两个地方复制:首先,在 {test_name}/main.rs 中生成代码;其次,在 {test_name}/user.rs 中,测试检查两个值是否相等。
  • 字段 test_values 是可选的,对于在 user.rs 断言中难以使用 value 的情况。而不是在整个结构体上断言相等,test_values 映射中的每个值都会生成单个断言。

许可证

MIT

依赖项

~1-2MB
~42K SLoC