ys memos

Blog

rustで簡単にBuilderパターンを自動実装する方法


rust

2023/09/29


Rustで構造体のフィールドは、インスタンス生成時に、すべてのフィールドを明示的に指定する必要がある。

それはRustの安全性や明示性を重視する姿勢と言え、基本的には望ましい設計である。

しかし、大量のフィールドを持つ構造体のインスタンス生成時に毎回全てのフィールドを明示的に書くのは冗長になることがある。特にデフォルト値を持たせたい場合や意識しないでいいフィールドを提供する際には問題になる。

その際に用いられるのが、Builderパターンであるが、その実装もフィールドが大量となるとそれにつれて増大する。 しかし、そんな悩みを解決するクレートがいくつか存在するので、自分がよく使う typed-builderを紹介する。



Cargo.tomlに以下の行を追加する。

最新バージョンはcrates.ioのtyped-builderページを見ると載っている。

Cargo.tlml
typed-builder = "0.16"

構造体定義の上につけるderiveに、TypedBuilderも加える。

#[derive(Debug, TypedBuilder)]
struct Person {
    age: u8,
    name: String,
    profile: Option<String>,
    interests: Vec<String>,
}

この設定では、以下のように利用することができる。

たった一行いじっただけで、このように簡単にBuilderを実装することが可能となった。

main.rs
fn main() {
    let me = Person::builder()
        .name("ysuzuki19".into())
        .age(1)
        .profile(None)
        .interests(vec![])
        .build();
    println!("{:?}", me);
}

上の利用方法だと、OptionフィールドもメソッドでNoneをセットする必要があるし、値を割り当てる場合はSome()が必要になる。しかし、それは冗長と言えよう。

そこで、各フィールドの設定をいじってみる。

main.rs
#[derive(Debug, TypedBuilder)]
struct Person {
    #[builder(default = 20)]
    age: u8,
    #[builder(default, setter(into))]
    name: String,
    #[builder(default, setter(strip_option, into))]
    profile: Option<String>,
    #[builder(default_code = "vec![\"Rust\".into()]")]
    interests: Vec<String>,
}

これを利用する場合、以下のようにできる。

main.rs
fn main() {
    let me = Person::builder()
        .name("ysuzuki19")
        .profile("Hello, world!")
        .build();
    println!("{:?}", me);
}

まず#[builder()]でフィールドの振る舞いを設定することができる。


defaultによって、フィールドのデフォルト値を設定できる。

これにより、.age()は呼び出さなくても.build()できるようになる。

#[builder(default = 20)]
age: u8,

この例ではdefault = 20としているが、単にdefaultとすればその型のデフォルト値になる。


setter(into)によって、.into()を省略できるようになる。

これにより、.name("ysuzuki19")と書けるようになる。

#[builder(default, setter(into))]
name: String,

setter(strip_option)によって、Some()を省略する。

これにより、.profile("Hello World!")とできるようになる。

#[builder(default, setter(strip_option, into))]
profile: Option<String>,

デフォルト値をコードで設定できる。

これにより、.interests()を呼び出さない限りはRustだけ興味がある事になる。

#[builder(default_code = "vec![\"Rust\".into()]")]
interests: Vec<String>,

以上の設定項目が、よく使う項目です。 マクロで自動実装してくれるのは非常に便利でいいですね!


関連タグを探す