Skip to content

About

Rust derive macro for type-state builders with compile-time guarantees for required fields and setter usage.

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Repository files navigation

builder-pattern

English | 日本語

Builder パターンの実装を生成する derive マクロです。

このプロジェクトは Rust の derive マクロを書く練習用です。

使用例

use builder_pattern_derive::Builder;

#[derive(Builder)]
struct Person {
    id: u64,
    name: String,
    dead: bool,
    another_id: Option<u64>,
    nums: Vec<u64>,
}

let p = Person::builder()
    .id(1)
    .name("name".to_owned())
    .dead()
    .another_id(2)
    .nums([3, 4, 5])
    .nums([6, 7, 8])
    .build();

assert_eq!(p.id, 1);
assert_eq!(p.name, "name");
assert_eq!(p.dead, true);
assert_eq!(p.another_id, Some(2));
assert_eq!(p.nums, [3, 4, 5, 6, 7, 8]);

生成される <構造体名>Builder 型と builder() メソッドの可視性は、元の構造体と同じです。 生成コードはランタイム支援 crate builder_pattern を参照するため、derive crate とともに直接依存関係に追加してください。Cargo 上の依存名を別名にしても利用できます。 名前付きフィールドを持つ構造体(フィールドが空の構造体を含む)に対応し、ジェネリックの場合は型・ライフタイム・const パラメーターと where 節を引き継ぎます。unit struct と tuple struct は対象外です。 ジェネリック型・const パラメーターに指定された既定値も保持します。

コンパイル時の保証

通常の必須フィールドは1回設定する必要があり、未設定や二重設定はコンパイルエラーになります。各フィールドのセッター呼び出し回数は次のとおりです。

フィールド セッター呼び出し回数
通常のフィールド ちょうど1回
bool / Option<T> 0回または1回
Vec<T> 0回以上
default 付き 0回または1回
fixed 付き 0回(セッターなし)
default / fixed のない、as_is を付けた特殊型 ちょうど1回

bool と Option<T> のセッターを複数回呼び出すことはできません。Vec<T> のセッターは何度でも呼び出せます。 特殊型に as_is を付けると通常のフィールドとして扱います。ただし default または fixed も指定した場合は、その初期値のルールが適用されます。 生成されるセッター名はすべて一意である必要があり、build は生成メソッド用に予約されています。重複や予約名は derive 時にエラーになります。 生成された Builder の戻り値を使わずに破棄すると、build() の呼び忘れに気づけるよう compiler warning が出ます。

次の例は、b フィールドが未設定のためコンパイルに失敗します。

#[derive(Builder)]
struct S {
    a: i64,
    b: i64,
}

S::builder()
    .a(1)
    .build(); // ここでコンパイルエラー

次の例は、b フィールドを2回設定しているためコンパイルに失敗します。

#[derive(Builder)]
struct S {
    a: i64,
    b: i64,
}

S::builder()
    .a(1)
    .b(2)
    .b(3) // ここでコンパイルエラー
    .build();

特別なフィールド

bool、Option、Vec のフィールドには特別な扱いがあります。 型エイリアスは展開せずパス名で判定します。bool / core::primitive::bool / std::primitive::bool、Option / core::option::Option / std::option::Option、Vec / std::vec::Vec を認識します。修飾パスには先頭に :: を付けることもできます(例: ::std::vec::Vec)。型エイリアスは通常の必須フィールドとして扱います。

bool フィールド

bool フィールドの初期値は false です。セッターは引数を取らず、呼び出すと値が true になります。

#[derive(Builder)]
struct S {
    flag: bool,
}

let s = S::builder().build();
assert_eq!(s.flag, false);

let s = S::builder().flag().build();
assert_eq!(s.flag, true);

Option フィールド

Option フィールドの初期値は None です。セッターには Option の内側の型の値を渡します。

#[derive(Builder)]
struct S {
    opt: Option<u64>,
}

let s = S::builder().build();
assert_eq!(s.opt, None);

let s = S::builder().opt(1).build();
assert_eq!(s.opt, Some(1));

Vec フィールド

Vec フィールドの初期値は空の Vec です。セッターには要素の値を渡します。セッターは何度でも呼び出せ、そのたびに要素が追加されます。 一括セッターは IntoIterator<Item = T> を受け取るため、配列や iterator chain を渡せます。

#[derive(Builder)]
struct S {
    nums: Vec<u64>,
}

let s = S::builder().build();
assert_eq!(s.nums, vec![]);

let s = S::builder().nums([1, 2, 3]).build();
assert_eq!(s.nums, vec![1, 2, 3]);

let s = S::builder().nums([1, 2, 3]).nums([4, 5, 6]).build();
assert_eq!(s.nums, vec![1, 2, 3, 4, 5, 6]);

フィールド属性

#[builder(...)] はフィールドに指定してください。構造体自体には指定できません。

1つのフィールドには #[builder(...)] を1つだけ指定してください。複数の設定は同じ属性内にまとめます。 設定のない #[builder()] は指定できません。 設定名は修飾せずに指定してください。

name

name 属性でセッター名を変更できます。 値には有効な Rust メソッド名を指定してください。キーワードを使う場合は r#type のような raw identifier を指定します。 従来の #[builder = "setter_name"] 形式も利用できます。 Vec フィールドで each と併用した場合、name は一括追加セッター名を変更し、each が要素ごとのセッター名を指定します。

#[derive(Builder)]
struct S {
    #[builder(name = "set_a")]
    a: u64,
}

let s = S::builder().set_a(1).build();
assert_eq!(s.a, 1);

as_is

as_is 属性を付けると、特別なフィールドも通常のフィールドとして扱います。each は Vec の特殊な扱いを使うため、同じフィールドには併用できません。 bool、Option、Vec に default または fixed を指定する場合も as_is が必要です。default のフィールドには省略可能なセッターが生成され、fixed のフィールドにはセッターが生成されません。

#[derive(Builder)]
struct S {
    #[builder(as_is)]
    bool: bool,
    #[builder(as_is)]
    option: Option<u64>,
    #[builder(as_is)]
    vec: Vec<u64>,
}

let s = S::builder()
    .bool(true)
    .option(None)
    .vec(vec![1, 2, 3])
    .build();
assert_eq!(s.bool, true);
assert_eq!(s.option, None);
assert_eq!(s.vec, vec![1, 2, 3]);

each

each 属性を付けると、Vec フィールドに要素を1つずつ追加するセッターを生成します。 値には有効な Rust メソッド名を指定してください。キーワードを使う場合は r#type のような raw identifier を指定します。

#[derive(Builder)]
struct S {
    #[builder(each = "num")]
    nums: Vec<u64>,
}

let s = S::builder()
    .num(1)
    .num(2)
    .num(3)
    .build();
assert_eq!(s.nums, vec![1, 2, 3]);

default

default 属性でフィールドの初期値を指定できます。値だけでなく式も指定できます。 式は通常の Rust 構文で指定できます。従来の文字列形式も引き続き使えます。文字列リテラルそのものを初期値にする場合は、文字列形式との区別のためブロック式で指定します(例: default = { let text = "text"; text })。fixed も同じ構文です。

#[derive(Builder)]
struct S {
    #[builder(default = 2u64.pow(2))]
    a: u64,
}

let s = S::builder().build();
assert_eq!(s.a, 4);

let s = S::builder().a(100).build();
assert_eq!(s.a, 100);

fixed

fixed 属性でフィールドの値を固定できます。値だけでなく式も指定できます。固定フィールドにはセッターがないため、name と each 属性は併用できません。

#[derive(Builder)]
struct S {
    #[builder(fixed = 2u64.pow(2))]
    a: u64,
}

let s = S::builder().build();
assert_eq!(s.a, 4);

ベンチマーク

nightly のベンチマーク機能を使わず、std::time::Instant と std::hint::black_box で計測します。release モードで実行してください。 4つの必須フィールドを持つ構造体の Builder と構造体リテラル、および each による4回の要素追加と Vec::push 4回を比較します。また、bool・Option・default 属性について、初期値を使う場合とセッターで上書きする場合を計測します。

cargo bench -p example --bench builder

1サンプルあたりの計測回数は BENCH_ITERS で変更できます(既定値は 1_000_000)。各比較を既定で11サンプル計測し、中央値・最小値・最大値を表示します。比較対象の実行順はサンプルごとに交互に入れ替えます。計測前に最大 10,000 回のウォームアップを行います。

BENCH_ITERS=100000 BENCH_SAMPLES=15 cargo bench -p example --bench builder

BENCH_SAMPLES でサンプル数を変更できます。

ライセンス

このプロジェクトは MIT License または Apache License 2.0 のいずれかの条件で利用できます。MIT License の著作者表記は GitHub アカウント @namba3 です。

About

Rust derive macro for type-state builders with compile-time guarantees for required fields and setter usage.

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages