Menu
Coddy logo textTech

where 句

CoddyのRustジャーニー「Object Oriented Programming」セクションの一部 — レッスン 42/61。

トレイト境界を増やすにつれて、関数のシグネチャが読みづらくなることがあります。それぞれが複数のトレイトを必要とする、複数のジェネリックパラメータを持つ関数を考えてみましょう:

fn complex_operation<T: Display + Clone, U: Debug + Summary>(first: T, second: U) {
    // ...
}

これは動作しますが、シグネチャが煩雑になってきています。where 句は、トレイト境界をパラメータリストの後に移動させることで、よりクリーンな代替手段を提供します:

fn complex_operation<T, U>(first: T, second: U)
where
    T: Display + Clone,
    U: Debug + Summary,
{
    // ...
}

どちらのバージョンも機能的には同じです。where 句は純粋に可読性のためのものです。各型の要件が個別の行に表示されるため、各ジェネリックパラメータに何が必要かを簡単に把握できるようになります。

where 節は、境界(bounds)が複雑な場合や、多くのジェネリックパラメータがある場合に特に価値を発揮します。これにより、関数名とパラメータを一目で確認できるようになり、制約は下に別途リストされます。これは、境界が単純な T: Trait を超える場合の、ほとんどの Rust コードベースにおける推奨されるスタイルです。

challenge icon

チャレンジ

簡単

複雑なトレイト境界の可読性を保つために where 句を使用するデータ処理システムを構築しましょう!複数の型パラメータを持ち、それぞれが異なるトレイトを必要とするジェネリック関数を作成し、where 構文を使用して境界をきれいに整理します。

コードは2つのファイルに分けて構成します:

  • processor.rs: 2つのパブリックなトレイトと、それら両方を使用するジェネリック関数を定義します:
    • Summarize&self を受け取り String を返す summary メソッドを持つトレイト
    • Validate&self を受け取り bool を返す validate メソッドを持つトレイト
    次に、異なるジェネリック型の2つのパラメータを受け取る process_items というパブリックなジェネリック関数を作成します。where 句を使用して、最初の型は CloneSummarize の両方を実装し、2番目の型は Validate を実装する必要があることを指定してください。この関数は、最初の項目のサマリーを出力し、次に2番目の項目が有効かどうかを出力する必要があります。
  • main.rs: 必要なトレイトを実装する2つのパブリックな構造体を作成します:
    • Article — パブリックな title フィールド (String) を持ちます。Summarize を実装して "Article: {}" を返し、Clone を派生(derive)させてください。
    • Form — パブリックな filled フィールド (bool) を持ちます。Validate を実装して filled の値を返すようにします。
    提供された入力を使用して Article と Form を作成し、両方を引数にして process_items を呼び出します。

where 句を使用すると、すべての境界をアングルブラケット(山括弧)の中に詰め込むよりも、関数シグネチャがはるかにすっきりします。各型の要件がそれぞれの行に表示されるため、各ジェネリックパラメータに何が必要かを簡単に確認できるようになります。

出力には、サマリーに続いてバリデーション結果が表示されるはずです:

Article: {title}
Valid: {true/false}

例えば、入力が "Rust Tips"true の場合:

Article: Rust Tips
Valid: true

入力が "Breaking News"false の場合:

Article: Breaking News
Valid: false

2つの入力を受け取ります:記事のタイトルと、フォームが入力済みかどうか(bool としてパースしてください)です。

チートシート

where 節は、制約をパラメータリストの後に移動させることで、ジェネリック関数のトレイト境界を指定するためのよりすっきりとした方法を提供します。

fn complex_operation<T, U>(first: T, second: U)
where
    T: Display + Clone,
    U: Debug + Summary,
{
    // ...
}

これは機能的にはインラインのトレイト境界と同一ですが、可読性が向上します:

fn complex_operation<T: Display + Clone, U: Debug + Summary>(first: T, second: U) {
    // ...
}

where 節は、特に以下のような場合に便利です:

  • 複数のジェネリックパラメータがある場合
  • 各パラメータに複数のトレイト境界が必要な場合
  • 関数のシグネチャを簡潔で読み取りやすく保ちたい場合

各型の要件がそれぞれの行に表示されるため、各ジェネリックパラメータが何を必要としているかを簡単に確認できます。これは、境界が単純な T: Trait を超える場合に、ほとんどのRustコードベースで好まれるスタイルです。

自分で試してみよう

mod processor;

use processor::{Summarize, Validate, process_items};
use std::io;

// TODO: 公開された title フィールド (String) を持つ公開構造体 Article を定義する
// Article に Clone を派生させる

// TODO: Article に Summarize を実装する
// summary メソッドは "Article: {title}" を返す必要がある

// TODO: 公開された filled フィールド (bool) を持つ公開構造体 Form を定義する

// TODO: Form に Validate を実装する
// validate メソッドは filled の値を返す必要がある

fn main() {
    let mut title = String::new();
    io::stdin().read_line(&mut title).expect("Failed to read line");
    let title = title.trim().to_string();

    let mut filled_input = String::new();
    io::stdin().read_line(&mut filled_input).expect("Failed to read line");
    let filled: bool = filled_input.trim().parse().expect("Failed to parse bool");

    // TODO: 与えられた title で Article を作成する
    
    // TODO: 与えられた filled の値で Form を作成する
    
    // TODO: article と form を引数にして process_items を呼び出す
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

Object Oriented Programmingのすべてのレッスン