Menu
Coddy logo textTech

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

CoddyのRustジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 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 を使って Iterate すると、各 shapeBox<dyn Shape> への参照になります。Shape トレイトの任意のメソッドを直接呼び出せます。Rust が動的ディスパッチを自動的に処理し、具体的な型ごとに正しい実装を呼び出します。

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

challenge icon

チャレンジ

簡単

異なる種類の item を保持できるショッピングカート用の価格計算機を作りましょう!異なる価格計算ロジックを持つ Product と Service を一緒に格納し、処理して合計価格を計算できるシステムを作成します。

コードは2つのファイルに分けて整理します。

  • items.rsprice method を持つ public な Priceable trait を Define します。この method は &self を受け取り、f64 を returns します。次に、2つの public な struct を作成します。
    • Product:public な name (String) フィールドと cost (f64) フィールドを持ちます。その price は単純に cost を returns する必要があります。
    • Service:public な name (String)、hourly_rate (f64)、hours (f64) フィールドを持ちます。その pricehourly_rate * hours を returns する必要があります。
  • main.rs:items module を取り込み、型 Vec<Box<dyn Priceable>> の vector を作成します。inputs を使用して1つの Product と1つの Service を作成し、それらを vector に add してから、collection を Iterate してすべての item の合計価格を calculate し、Print します。

ここで trait objects の力が発揮されます。Iteration code は、Product を処理しているのか Service を処理しているのかを知る必要がありません。各 item に対して単に price() を呼び出すだけで、Rust が正しい実装へ自動的に dispatch します。

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

Total: ${total}

たとえば、inputs が Laptop999.99Consulting150.03.0 の場合:

Total: $1449.99

また、inputs が Book29.99Tutoring50.02.0 の場合:

Total: $129.99

5つの input を受け取ります:product name、product cost、service name、hourly rate、worked hours(数値は f64 として parse します)。

自分で試してみよう

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腕試し

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

オブジェクト指向プログラミングのすべてのレッスン

自分で練習してみよう: Rustオンラインコンパイラ