複数の境界
CoddyのRustジャーニー「Object Oriented Programming」セクションの一部 — レッスン 41/61。
時には、単一のトレイト境界だけでは不十分なことがあります。表示が可能で、かつ要約を提供できるジェネリック型が必要になるかもしれません。Rustでは、+構文を使用して複数のトレイトを要求することができます。
型が2つのトレイトを実装する必要があることを指定する方法は次のとおりです:
use std::fmt::Display;
trait Summary {
fn summarize(&self) -> String;
}
fn announce<T: Display + Summary>(item: T) {
println!("Breaking news: {}", item);
println!("Summary: {}", item.summarize());
}
T: Display + Summary という制約は、「Tが Display と Summary の両方を実装していなければならない」ことを意味します。関数内では、{} による表示(Display 由来)と summarize() の呼び出し(Summary 由来)という、両方のトレイトの機能を使用できます。
必要な数だけトレイトを繋げることができます:
fn process<T: Display + Summary + Clone>(item: T) {
// 表示、要約、そしてクローンが可能です
}
このパターンは、関数が複数の振る舞いに依存する場合に不可欠です。任意の型を受け取ってうまくいくことを期待するのではなく、必要な機能を正確に明示的に宣言し、コンパイラがコンパイル時にそれを強制します。
チャレンジ
簡単アイテムが複数の機能を持つことを要求する製品検査システムを構築しましょう!カスタムトレイトと標準トレイトの両方を実装する型のみを受け入れるジェネリック関数を作成し、+ 構文がどのように複数の境界を組み合わせるかを確認します。
コードを2つのファイルに分けて構成します:
product.rs:&selfを受け取り、検査の詳細を含むStringを返すinspectというメソッドを持つ、パブリックなInspectableトレイトを定義します。次に、パブリックなname(String) フィールドとserial(u32) フィールドを持つパブリックなGadget構造体を作成します。Gadget はInspectable("Inspecting: {}"を返す) とstd::fmt::Display("{} (SN: {})"としてフォーマットする) の両方を実装する必要があります。最後に、DisplayとInspectableの両方を実装する任意の型Tを受け入れる、full_reportというパブリックなジェネリック関数を作成します。この関数は2行出力する必要があります。1行目は{}フォーマッタを使用したアイテム、2行目はinspect()を呼び出した結果です。main.rs: product モジュールを取り込み、提供された入力を使用してGadgetインスタンスを作成します。作成したガジェットでfull_reportを呼び出し、両方のトレイト要件を満たしていることを示します。
複数境界の威力は、full_report 関数が両方のトレイトの機能(アイテムを綺麗に表示すること、および検査の詳細を取得すること)を使用できることにあり、これらはすべてコンパイル時に保証されます。
出力には、表示フォーマットと検査結果の両方が表示されるはずです:
{name} (SN: {serial})
Inspecting: {name}例えば、入力が Smartwatch と 98765 の場合:
Smartwatch (SN: 98765)
Inspecting: Smartwatchガジェット名とシリアル番号(u32 としてパース)の2つの入力を受け取ります。
チートシート
+ 構文を使用して、ジェネリック型に複数のトレイト境界を要求できます。
fn function_name<T: Trait1 + Trait2>(item: T) {
// 両方のトレイトの機能を使用できます
}
Display とカスタムトレイトを使用した例です:
use std::fmt::Display;
trait Summary {
fn summarize(&self) -> String;
}
fn announce<T: Display + Summary>(item: T) {
println!("Breaking news: {}", item);
println!("Summary: {}", item.summarize());
}
必要に応じて複数のトレイトを連結できます:
fn process<T: Display + Summary + Clone>(item: T) {
// 表示、要約、そしてクローンが可能です
}
コンパイラは、その型が指定されたすべてのトレイトを実装していることをコンパイル時に強制します。
自分で試してみよう
mod product;
use product::{Gadget, full_report};
fn main() {
// 入力を読み取る
let mut name = String::new();
std::io::stdin().read_line(&mut name).expect("Failed to read line");
let name = name.trim().to_string();
let mut serial_input = String::new();
std::io::stdin().read_line(&mut serial_input).expect("Failed to read line");
let serial: u32 = serial_input.trim().parse().expect("Failed to parse serial");
// TODO: 名前とシリアル番号を使用してGadgetインスタンスを作成する
// TODO: 作成したgadgetを使用してfull_reportを呼び出す
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。