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 コードベースにおける推奨されるスタイルです。
チャレンジ
簡単複雑なトレイト境界の可読性を保つために where 句を使用するデータ処理システムを構築しましょう!複数の型パラメータを持ち、それぞれが異なるトレイトを必要とするジェネリック関数を作成し、where 構文を使用して境界をきれいに整理します。
コードは2つのファイルに分けて構成します:
processor.rs: 2つのパブリックなトレイトと、それら両方を使用するジェネリック関数を定義します:Summarize—&selfを受け取りStringを返すsummaryメソッドを持つトレイトValidate—&selfを受け取りboolを返すvalidateメソッドを持つトレイト
process_itemsというパブリックなジェネリック関数を作成します。where句を使用して、最初の型はCloneとSummarizeの両方を実装し、2番目の型はValidateを実装する必要があることを指定してください。この関数は、最初の項目のサマリーを出力し、次に2番目の項目が有効かどうかを出力する必要があります。main.rs: 必要なトレイトを実装する2つのパブリックな構造体を作成します:Article— パブリックなtitleフィールド (String) を持ちます。Summarizeを実装して"Article: {}"を返し、Cloneを派生(derive)させてください。Form— パブリックなfilledフィールド (bool) を持ちます。Validateを実装してfilledの値を返すようにします。
process_itemsを呼び出します。
where 句を使用すると、すべての境界をアングルブラケット(山括弧)の中に詰め込むよりも、関数シグネチャがはるかにすっきりします。各型の要件がそれぞれの行に表示されるため、各ジェネリックパラメータに何が必要かを簡単に確認できるようになります。
出力には、サマリーに続いてバリデーション結果が表示されるはずです:
Article: {title}
Valid: {true/false}例えば、入力が "Rust Tips" と true の場合:
Article: Rust Tips
Valid: true入力が "Breaking News" と false の場合:
Article: Breaking News
Valid: false2つの入力を受け取ります:記事のタイトルと、フォームが入力済みかどうか(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 を呼び出す
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。