Menu
Coddy logo textTech

トレイトオブジェクトの反復処理

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

Vec<Box<dyn Trait>>に異なる型を格納できるようになったので、次のステップはそのコレクションを処理することです。トレイトオブジェクトのイテレーションは、他のベクタのイテレーションとまったく同じように機能します。つまり、各要素をループで回し、トレイトで定義されたメソッドを呼び出します。

trait Shape {
    fn area(&self) -> f64;
}

struct Circle { radius: f64 }
struct Rectangle { width: f64, height: f64 }

impl Shape for Circle {
    fn area(&self) -> f64 { 3.14159 * self.radius * self.radius }
}

impl Shape for Rectangle {
    fn area(&self) -> f64 { self.width * self.height }
}

fn main() {
    let shapes: Vec<Box<dyn Shape>> = vec![
        Box::new(Circle { radius: 2.0 }),
        Box::new(Rectangle { width: 3.0, height: 4.0 }),
        Box::new(Circle { radius: 1.0 }),
    ];

    for shape in &shapes {
        println!("Area: {}", shape.area());
    }
}

for shape in &shapes で反復処理を行うと、各 shapeBox<dyn Shape> への参照になります。Shape トレイトの任意のメソッドを直接呼び出すことができます。Rust は動的ディスパッチを自動的に処理し、各具体的な型に対して正しい実装を呼び出します。

このパターンは、結果を集計するのに強力です。すべての面積を合計したり、サイズで図形をフィルタリングしたり、コレクションを変換したりすることができます。重要な洞察は、アイテムがトレイトオブジェクトのコレクションに入ると、元の型に関係なく、純粋にトレイトのインターフェースを通じてそれらを操作することになるという点です。

challenge icon

チャレンジ

簡単

異なる種類のアイテムを保持できるショッピングカートの価格計算機を作成しましょう!製品とサービスという、それぞれ異なる価格ロジックを持つアイテムを一緒に保存し、合計価格を計算するために処理できるシステムを作成します。

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

  • items.rs: &self を受け取り f64 を返す price メソッドを持つ、パブリックな Priceable トレイトを定義します。次に、2つのパブリックな構造体を作成します:
    • Product — パブリックな name (String) と cost (f64) フィールドを持ちます。その price は単に cost を返します。
    • Service — パブリックな name (String)、hourly_rate (f64)、および hours (f64) フィールドを持ちます。その pricehourly_rate * hours を返します。
  • main.rs: items モジュールを取り込み、Vec<Box<dyn Priceable>> 型のベクタを作成します。入力値を使用して1つの Product と1つの Service を作成し、それらをベクタに追加します。その後、コレクションを反復処理して、すべてのアイテムの合計価格を計算し、表示します。

ここでトレイトオブジェクトの力が発揮されます。反復処理のコードは、処理しているのが Product なのか Service なのかを知る必要はありません。単に各アイテムの price() を呼び出すだけで、Rust が自動的に正しい実装をディスパッチします。

出力は、合計価格を小数点以下1桁で表示する必要があります:

Total: ${total}

例えば、入力が Laptop, 999.99, Consulting, 150.0, 3.0 の場合:

Total: $1449.99

また、入力が Book, 29.99, Tutoring, 50.0, 2.0 の場合:

Total: $129.99

5つの入力を受け取ります:製品名、製品のコスト、サービス名、時間単価、および作業時間(数値は f64 としてパースしてください)。

チートシート

トレイトオブジェクトのコレクションを反復処理するには、ベクタへの参照とともに標準的な for ループを使用します:

for item in &collection {
    item.trait_method();
}

for item in &vec を使用して反復処理を行う場合、各 itemBox<dyn Trait> への参照になります。トレイトの任意のメソッドを直接呼び出すことができます。Rustは動的ディスパッチを自動的に処理し、各具体的な型に対して正しい実装を呼び出します。

結果を集計するためにトレイトオブジェクトを反復処理する例:

trait Shape {
    fn area(&self) -> f64;
}

struct Circle { radius: f64 }
struct Rectangle { width: f64, height: f64 }

impl Shape for Circle {
    fn area(&self) -> f64 { 3.14159 * self.radius * self.radius }
}

impl Shape for Rectangle {
    fn area(&self) -> f64 { self.width * self.height }
}

fn main() {
    let shapes: Vec<Box<dyn Shape>> = vec![
        Box::new(Circle { radius: 2.0 }),
        Box::new(Rectangle { width: 3.0, height: 4.0 }),
    ];

    for shape in &shapes {
        println!("Area: {}", shape.area());
    }
}

このパターンは結果を集計するのに便利です。トレイトのインターフェースのみを通じてアイテムを操作しながら、値を合計したり、基準でフィルタリングしたり、コレクションを変換したりできます。

自分で試してみよう

mod items;

use items::{Priceable, Product, Service};

fn main() {
    // 入力を読み込む
    let mut product_name = String::new();
    std::io::stdin().read_line(&mut product_name).expect("Failed to read line");
    let product_name = product_name.trim().to_string();

    let mut product_cost = String::new();
    std::io::stdin().read_line(&mut product_cost).expect("Failed to read line");
    let product_cost: f64 = product_cost.trim().parse().expect("Failed to parse");

    let mut service_name = String::new();
    std::io::stdin().read_line(&mut service_name).expect("Failed to read line");
    let service_name = service_name.trim().to_string();

    let mut hourly_rate = String::new();
    std::io::stdin().read_line(&mut hourly_rate).expect("Failed to read line");
    let hourly_rate: f64 = hourly_rate.trim().parse().expect("Failed to parse");

    let mut hours = String::new();
    std::io::stdin().read_line(&mut hours).expect("Failed to read line");
    let hours: f64 = hours.trim().parse().expect("Failed to parse");

    // TODO: product_name と product_cost を使用して Product を作成する

    // TODO: service_name、hourly_rate、および hours を使用して Service を作成する

    // TODO: Vec<Box<dyn Priceable>> を作成し、両方のアイテムをそれに追加する

    // TODO: ベクタを反復処理し、合計金額を計算する

    // TODO: 小数点第1位まで合計を出力する
    // 形式: Total: ${total:.1}
}
quiz icon腕試し

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

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