#derive-builder #builder #const-generics #compile-time #macro-derive #struct #validation

const_typed_builder

使用 const generics 的编译时类型检查的 builder derive

4 个版本 (2 个破坏性更新)

0.3.0 2023 年 10 月 3 日
0.2.0 2023 年 9 月 20 日
0.1.1 2023 年 9 月 13 日
0.1.0 2023 年 9 月 13 日

#1749 in Rust 模式

每月 39 次下载
用于 ansi_parser_extended

MIT 许可证

30KB
174

Builder Derive 宏文档

Builder derive 宏用于为 Rust 中的 struct 生成 builder 方法,这个 crate 的最大特点是它提供了对 struct 的编译时验证。用户可以采用一些配置来定义复杂 struct 的有效性,并在 struct 创建之前进行检查。

该库还检查了 struct 何时永远有效或始终无效的情况,但这仍在进行中。错误总是被发出,但警告仅在夜间通道上发出。这是由于 proc_macro_error 的限制。

先决条件

要使用 Builder derive 宏,您应该在项目的 Cargo.toml 文件中将 const_typed_builder crate 添加到项目的依赖项中

[dependencies]
const_typed_builder = "0.3"

同时,请确保您的代码中有以下使用声明

use const_typed_builder::Builder;

概述

该 crate 可用于在编译时检查 struct 的有效性。用户必须在 struct 有效之前调用 build。这是通过检查所有必需字段是否实例化以及所有用户定义的 "组" 是否有效来完成的。

示例

基本用法

use const_typed_builder::Builder;

#[derive(Builder)]
pub struct Foo {
    bar: String,
}

let foo = Foo::builder()
    .bar("Hello world!".to_string()) // <- The program would not compile without this call
    .build();                        // <- Because this function is only implemented for the
                                     // .. version of FooBuilder where `bar` is initialized

launchd 的子集

use const_typed_builder::Builder;
use std::path::PathBuf;

#[derive(Builder)]
pub struct ResourceLimits {
    core: Option<u64>,
    // ...
}
#[derive(Builder)]
#[group(program = at_least(1))]
pub struct Launchd {
    #[builder(mandatory)]
    label: Option<String>,
    disabled: Option<bool>,
    user_name: Option<String>,
    group_name: Option<String>,
    // ...
    #[builder(group = program)]
    program: Option<PathBuf>,
    bundle_program: Option<String>,
    #[builder(group = program)]
    program_arguments: Option<Vec<String>>,
    // ...
    #[builder(skip)]
    on_demand: Option<bool>, // NB: deprecated (see KeepAlive), but still needed for reading old plists.
    #[builder(skip)]
    service_ipc: Option<bool>, // NB: "Please remove this key from your launchd.plist."
    // ...
    #[builder(propagate)]
    soft_resource_limits: Option<ResourceLimits>,
    #[builder(propagate)]
    hard_resource_limits: Option<ResourceLimits>,
    // ...
}

let launchd = Launchd::builder()
    .label("my_label".to_string())    // <- 1: Mandatory
    .program("./my_program".into())   // <- 2: Launchd expects that least one of these fields is set..
    .program_arguments(               // <- 2: .. We can remove either one, but never both
        vec!["my_arg".to_string()]
    ) 
//  .on_demand(false)                    <- 3: This function doesn't exist
    .soft_resource_limits(|builder|
        Some(builder.core(Some(1)).build()) // <- 4: Propagating to `ResourceLimits::builder`
    ) 
    .build();

属性

这是对这个库中功能的快速概述。有关所有功能的更深入解释,包括示例,请参阅 const_typed_builder_derive::BuilderStruct

  • #[builder(assume_mandatory)]:表示结构体中的所有字段都应被视为必填项。如果不提供等号(例如,#[builder(assume_mandatory)]),则将字段的 mandatory 标志设置为 true。如果提供等号(例如,#[builder(assume_mandatory = true)]),则根据值设置字段的 mandatory 标志。
  • #[group(group_name = (exact(N)|at_least(N)|at_most(N)|single)]:将结构体的字段与名为 "group_name" 的组关联,并指定组的操作行为。 group_name 应该是一个字符串标识符。该组可以有以下几种操作行为
    • exact(N):在构建器构建过程中,组中必须设置恰好 N 个字段。
    • at_least(N):在构建器构建过程中,组中至少必须设置 N 个字段。
    • at_most(N):在构建器构建过程中,组中最多可以设置 N 个字段。
    • single:在构建器构建过程中,组中只能设置一个字段。这是 exact(1) 的简写。例如,#[group(foo = at_least(2))] 创建了一个组,其中至少需要初始化 2 个字段。
  • #[builder(solver = (brute_force|compiler))]: 请谨慎使用,请参阅此文件底部注释!指定构建结构时使用的求解器类型。`solve_type` 应为预定义的求解器类型之一,例如 `brute_force` 或 `compiler`。如果提供等于号(例如,`#[builder(solver = brute_force)]`),则设置相应的“求解器类型”。此属性仍在测试中,`brute_force` 是默认值,只有在编译时间有问题时,您可以尝试使用 `compiler`。但 `compiler` 提供的保证较少。

字段

  • #[builder(group = group_name)]: 该库的核心。将字段与名为 `group_name` 的组关联。同一组中的字段被视为一个单元,在构建器构建过程中至少必须设置其中一个。此属性允许将组名指定为标识符(例如,`group = my_group`)或字符串(例如,`group = "my_group"`)。
  • #[builder(mandatory)]: 标记字段为必填项,意味着在构建器构建过程中必须设置它。如果没有提供等于号(例如,`#[builder(mandatory)]`),则将字段设置为必填项。如果提供等于号(例如,`#[builder(mandatory = true)]`),则根据值设置必填标志。
  • #[builder(optional)]:将字段标记为可选,这与#[builder(mandatory)]正好相反。如果不提供等号(例如,#[builder(optional)]),则将字段设置为可选。如果提供等号(例如,#[builder(optional = true)]),则根据值设置可选标志。
  • #[builder(skip)]:将字段标记为跳过,这意味着构建器将不包括它。这可以用于已弃用但必须反序列化的字段。这样,您可以确保新结构体永远不会使用此字段初始化,但旧结构体仍然可以使用。为了实现这一点,字段类型必须是Option<T>
  • #[builder(propagate)]:表示字段在构建器构建时应传播其值。如果存在此属性,则当使用构建器构建对象时,字段值将被复制或移动到构建的对象中。

字段可以是组的一部分、必填、可选或跳过。这些属性是互斥的。propagate可以用于任何类型也派生自Builder的字段。

[!注意]检查每个字段的合法性是与SAT问题直接相关的,SAT问题是一个NP完全问题。这对分组字段尤其有影响。当前默认的实现是brute_force,这个实现目前的复杂度是$O(2^g),其中$g是分组变量的数量。这对几个字段来说不是问题,但可能会显著影响编译时间。这仍然可以进行显著的优化。未来的版本可能会提高这个复杂度。

另一种实现是 compiler。我还没有测试其速度提升,但可能存在一些问题。尽管我还没有能够重现该问题,但似乎const值并不保证在编译时被评估。这导致的问题是在编译时组验证不一定失败。

用户可以通过添加以下代码到结构体上方来选择使用 compiler 解析器,#[builder(solver = compiler)] 来启用。我对其性能不提供任何保证。

欢迎任何愿意帮助,并添加SAT解析器作为依赖(在特性标志之后)的人!

灵感

构建器宏以前已经做过,但并不完全符合我的使用场景。还可以看看derive_buildertyped-builder。这些项目目前更为成熟,但任何愿意测试这个crate的人都是天赐良机。

依赖项

~1.3–1.9MB
~36K SLoC