Menu
Coddy logo textTech

複数の境界

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が DisplaySummary の両方を実装していなければならない」ことを意味します。関数内では、{} による表示(Display 由来)と summarize() の呼び出し(Summary 由来)という、両方のトレイトの機能を使用できます。

必要な数だけトレイトを繋げることができます:

fn process<T: Display + Summary + Clone>(item: T) {
    // 表示、要約、そしてクローンが可能です
}

このパターンは、関数が複数の振る舞いに依存する場合に不可欠です。任意の型を受け取ってうまくいくことを期待するのではなく、必要な機能を正確に明示的に宣言し、コンパイラがコンパイル時にそれを強制します。

challenge icon

チャレンジ

簡単

アイテムが複数の機能を持つことを要求する製品検査システムを構築しましょう!カスタムトレイトと標準トレイトの両方を実装する型のみを受け入れるジェネリック関数を作成し、+ 構文がどのように複数の境界を組み合わせるかを確認します。

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

  • product.rs: &self を受け取り、検査の詳細を含む String を返す inspect というメソッドを持つ、パブリックな Inspectable トレイトを定義します。次に、パブリックな name (String) フィールドと serial (u32) フィールドを持つパブリックな Gadget 構造体を作成します。Gadget は Inspectable ("Inspecting: {}" を返す) と std::fmt::Display ("{} (SN: {})" としてフォーマットする) の両方を実装する必要があります。最後に、DisplayInspectable の両方を実装する任意の型 T を受け入れる、full_report というパブリックなジェネリック関数を作成します。この関数は2行出力する必要があります。1行目は {} フォーマッタを使用したアイテム、2行目は inspect() を呼び出した結果です。
  • main.rs: product モジュールを取り込み、提供された入力を使用して Gadget インスタンスを作成します。作成したガジェットで full_report を呼び出し、両方のトレイト要件を満たしていることを示します。

複数境界の威力は、full_report 関数が両方のトレイトの機能(アイテムを綺麗に表示すること、および検査の詳細を取得すること)を使用できることにあり、これらはすべてコンパイル時に保証されます。

出力には、表示フォーマットと検査結果の両方が表示されるはずです:

{name} (SN: {serial})
Inspecting: {name}

例えば、入力が Smartwatch98765 の場合:

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を呼び出す
}
quiz icon腕試し

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

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