#derive-builder #builder #compile-time #group #struct #const #fields

const_typed_builder_derive

使用 const 泛型进行编译时类型检查的构建器派生

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 日

#25 in #derive-builder


2 个 crate 中使用 (通过 const_typed_builder)

MIT 许可证

98KB
1.5K SLoC

Builder 派生宏文档

Builder 派生宏用于为 Rust 中的结构体生成构建器方法,该库最大的特点是它在编译时对结构体进行验证。用户可以采用几种配置来定义复杂结构体的有效性,并在结构体创建之前进行检查。

库还会检查结构体永远无法有效或始终有效的场景,但这仍在进行中。错误总是会被发出,但警告仅在夜间渠道上发出。这是由于 proc_macro_error 的限制。

先决条件

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

[dependencies]
const_typed_builder = "0.3"

此外,请确保你的代码中有以下使用语句

use const_typed_builder::Builder;

概述

该 crate 可以用于在编译时检查结构体的有效性。用户只能在结构体有效后才能调用构建。这是通过检查所有必填字段是否实例化以及所有用户定义的 "组" 是否有效来完成的。

示例

基本用法

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::Builder结构体

  • #[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_forcecompiler。如果提供等号(例如,#[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问题相关,这是一个NP完全问题。这尤其影响了分组字段。当前分组字段正确性检查的默认实现是brute_force,当前实现具有$O(2^g)$的复杂性,其中$g$是分组变量的数量。这对几个字段来说不是问题,但可能会显著影响编译时间。这仍然可以显著优化。未来的版本可能会改善这种复杂性。

另一种实现是 compiler。我还没有测试其速度提升,但它可能存在一些问题。虽然我还没有能够重现这个问题,但似乎const值并不保证在编译时进行评估。这导致问题在于组验证并不保证在编译时失败。

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

任何愿意帮助并添加SAT求解器作为依赖(在功能标志之后)的人都可以这样做!

灵感

之前已经实现过构建器宏,但并不完全符合我的使用场景。还可以查看derive_buildertyped-builder。这些项目目前更为成熟,但任何愿意测试这个crate的人都是一大帮助。

依赖项

~1.3–1.9MB
~35K SLoC