404 Not Found

404 Not Found


nginx

Rustのトレイト:共通の挙動の定義

トレイトは、Rustにおける「インターフェース」に相当するものです。トレイトは一連のメソッドのシグネチャを定義し、異なる型が同じトレイトを実装することで、同じ動作契約を共有することができます。

具体的なタイプが「そのものが何であるか」を表すのに対し、特性は「そのものが何ができるか」を表します。例えばUSB-Cポートを例に挙げましょう。スマートフォン、ノートパソコン、タブレットのいずれであっても、USB-Cに対応していれば、接続して充電することができます。これらのデバイスの内部構造は大きく異なるかもしれませんが、ユーザーに約束される動作は一貫しています。


1. 学習内容


2. 概念図

以下のマーメイド図は、ある特性に関する関係性の完全な連鎖――その定義から、さまざまな型に対する実装、そして最終的にはポリモーフィックな呼び出しに至るまで――を示しています。

100%
graph LR
    A["trait UsbCCharge"] -->|Defining a Behavioral Contract| B["fn charge(&self)"]
    A --> C["fn voltage(&self) -> u32"]
    A --> D["fn charge_time(&self) -> String"]
    B --> E["impl for Phone<br/>charge: 18W"]
    B --> F["impl for Laptop<br/>charge: 65W"]
    B --> G["impl for Tablet<br/>charge: 30W"]
    E --> H["phone.charge()"]
    F --> I["laptop.charge()"]
    G --> J["tablet.charge()"]
    H --> K["Polymorphic Call<br/>The Same trait Interface<br/>Different types of behavior vary"]
    I --> K
    J --> K

3. ユニバーサル充電器の物語

(1) 面倒な点:デバイスごとに充電方法が異なる

レオ(Leo)は、ある電子機器メーカーの設計者です。同社の製品ラインナップは、スマートフォン、ノートパソコン、タブレットへと拡大しており、各デバイスにはそれぞれ独自の充電ロジックが採用されています。

当初、彼はデバイスごとに個別に充電コードを書いていました:

RUST
struct Phone;
struct Laptop;
struct Tablet;

impl Phone {
    fn charge(&self) {
        println!("Phone: charging via USB-C at 18W");
    }
}

impl Laptop {
    fn charge(&self) {
        println!("Laptop: charging via USB-C at 65W");
    }
}

impl Tablet {
    fn charge(&self) {
        println!("Tablet: charging via USB-C at 30W");
    }
}

fn main() {
    let phone = Phone;
    let laptop = Laptop;
    let tablet = Tablet;

    phone.charge();
    laptop.charge();
    tablet.charge();
}

3つの構造体にはそれぞれchargeメソッドがありますが、これらは名前とシグネチャは同じでも、互いに「整合性」はありません。もし、あらゆるデバイスを充電できる「汎用充電ステーション」関数を記述しようとしても、それは不可能です。各デバイスタイプは独立しており、共通の抽象レイヤーが存在しないからです。

(2) より複雑な要件:バッチ充電

プロダクトマネージャーは、複数の異なる種類のデバイスを同時に充電できる「ユニバーサル充電ステーション」の開発を依頼しました:

RUST
// This code cannot be compiled.——Unknown parameter type
// fn charge_all_devices(devices: ???) {
//     for device in devices {
//         device.charge();
//     }
// }

トレイトがないと、charge_all_devicesは型が混在するコレクションを受け入れることができません。各型ごとに関数をオーバーロードするか、あるいは諦めるしかありません。

(3) Rustのトレイトを用いたアプローチ

RUST
// Define a trait: a shared behavior contract
trait UsbCCharge {
    fn charge(&self);
    fn voltage(&self) -> u32 { 18 }  // Default method with default voltage
}

struct Phone;
struct Laptop;
struct Tablet;

impl UsbCCharge for Phone {
    fn charge(&self) {
        println!("Phone: charging via USB-C at {}W", self.voltage());
    }
    // voltage() uses the default (18W)
}

impl UsbCCharge for Laptop {
    fn charge(&self) {
        println!("Laptop: charging via USB-C at {}W", self.voltage());
    }
    fn voltage(&self) -> u32 { 65 }  // Override default
}

impl UsbCCharge for Tablet {
    fn charge(&self) {
        println!("Tablet: charging via USB-C at {}W", self.voltage());
    }
    fn voltage(&self) -> u32 { 30 }  // Override default
}

// Generic function: accepts any type that implements UsbCCharge
fn charge_device<T: UsbCCharge>(device: &T) {
    device.charge();
}

fn main() {
    charge_device(&Phone);
    charge_device(&Laptop);
    charge_device(&Tablet);
}

出力:

TEXT
Phone: charging via USB-C at 18W
Laptop: charging via USB-C at 65W
Tablet: charging via USB-C at 30W

trait UsbCCharge は「充電動作の契約」を定義しています。このトレイトを実装する型であれば、charge_device によって受け入れられます。voltage() にはデフォルトの実装(18W)がありますが、Laptop と Tablet はこの実装をオーバーライドすることを選択しています。これこそがトレイトの核心的な価値、すなわち共通の動作を定義しつつ、実装の差異を許容することです。


4. 基本概念

(1) 特性システムの概要

100%
graph TB
    A[Rust Trait System] --> B[Trait Definition]
    A --> C[Implementing Traits]
    A --> D[Derivable Traits]
    A --> E[Trait as Parameters]
    A --> F[Trait Objects]
    A --> G[Trait Inheritance]

    B --> B1["trait Name { fn method(&self); }"]
    C --> C1["impl TraitName for MyType { ... }"]
    C --> C2["Default methods in trait"]

    D --> D1["#[derive(Debug, Clone, Copy, PartialEq)]"]
    D --> D2["Compiler auto-generates implementation"]

    E --> E1["fn foo(x: impl Trait)"]
    E --> E2["fn foo<T: Trait>(x: T)"]
    E --> E3["fn foo<T>(x: T) where T: Trait"]

    F --> F1["Box<dyn Trait>"]
    F --> F2["Runtime dispatch (vtable)"]

    G --> G1["trait A: SuperTrait { }"]
    G --> G2["Inherits methods from SuperTrait"]

(2) 静的配布と動的配布

特集 ジェネリック制約 T: Trait / impl Trait トレイトのインスタンス dyn Trait
配布タイミング コンパイル時 (静的配布) 実行時 (動的配布)
実装 シングルトン — 型ごとに個別のコードを生成 仮想テーブル (vtable) — ポインタを介した間接呼び出し
パフォーマンス オーバーヘッドなし(インライン化可能) 間接呼び出しによるオーバーヘッド
バイナリサイズ 大きい(型ごとに1コピー) 小さい(コードが1コピー)
型の要件 具体的な型は呼び出し時に決定される 同じトレイトを実装している限り、型が異なっていても構わない
ユースケース パフォーマンスが重要で、型がコンパイル時に判明している場合 異種コレクションが必要で、型が実行時に決定される場合

(3) よく使われる派生形質

特性 機能 自動的に生成される挙動
Debug フォーマット付き出力 {:?} デバッグ出力を生成;構造体のフィールド名と値を出力
Clone .clone() を明示的にコピー フィールドごとにコピーして clone メソッドを生成
Copy 暗黙のコピー(ビット単位のコピー) 代入の際、所有権は移転されず、代わりに値がコピーされる
PartialEq 等価比較 == / != eq メソッドの生成、フィールドごとの比較
Eq 全等価(数学的等価関係) PartialEq に基づき、反射性を追加で保証したもの
Hash ハッシュ計算 hash を生成する方法。フィールドごとにハッシュ値を計算する
Default デフォルト値 各フィールドのデフォルト値を使用して、default メソッドを生成します

(4) 形質制約の組み合わせに関するクイックリファレンス

制約の種類 構文 使用例
インライン単一制約 fn foo<T: Trait>(x: T) 単純単一制約 fn charge<T: UsbCCharge>(d: &T)
インラインの複数制約 fn foo<T: Trait1 + Trait2>(x: T) 複数制約 fn describe<T: Debug + UsbCCharge>(d: &T)
WHERE句 fn foo<T>(x: T) where T: Trait 複合制約、複数型のパラメータ where T: UsbCCharge, U: Debug
impl トレイト fn foo(x: impl Trait) シンプルな構文シュガー fn plug(d: &impl USBDevice)
impl トレイト + fn foo(x: impl Trait1 + Trait2) シンプルかつ制約付き fn describe(d: &(impl USBDevice + Debug))

5. ストロークの例

(1) ▶ サンプル:トレイトの定義とデフォルトメソッド — デバイス充電器 (難易度 ⭐)

RUST
// ============================================
// Trait definition with default methods
// ============================================

// Define a trait: any device that can charge via USB-C
trait UsbCCharge {
    // Required method: must be implemented
    fn charge(&self);

    // Default method: implementor MAY override
    fn voltage(&self) -> u32 {
        18  // Default: standard USB-C 18W
    }

    // Another default method using self.voltage()
    fn charge_time(&self, battery_capacity_mah: u32) -> String {
        let hours = battery_capacity_mah as f64 / (self.voltage() as f64 * 1000.0 / 5.0);
        format!("{:.1} hours to full charge", hours)
    }
}

struct Phone;
struct Laptop;
struct Tablet;

impl UsbCCharge for Phone {
    fn charge(&self) {
        // Uses the default voltage() -> 18W
        println!("Phone: charging at {}W (standard speed)", self.voltage());
    }
    // voltage() uses default, charge_time() uses default
}

impl UsbCCharge for Laptop {
    fn charge(&self) {
        println!("Laptop: charging at {}W (fast charging)", self.voltage());
    }

    fn voltage(&self) -> u32 {
        65  // Laptop needs more power
    }
}

impl UsbCCharge for Tablet {
    fn charge(&self) {
        println!("Tablet: charging at {}W (medium speed)", self.voltage());
    }

    fn voltage(&self) -> u32 {
        30
    }
}

fn main() {
    let phone = Phone;
    let laptop = Laptop;
    let tablet = Tablet;

    phone.charge();
    println!("  -> {}", phone.charge_time(3000));

    laptop.charge();
    println!("  -> {}", laptop.charge_time(6000));

    tablet.charge();
    println!("  -> {}", tablet.charge_time(5000));
}

出力:

TEXT
Phone: charging at 18W (standard speed)
  -> 0.8 hours to full charge
Laptop: charging at 65W (fast charging)
  -> 0.5 hours to full charge
Tablet: charging at 30W (medium speed)
  -> 0.8 hours to full charge

UsbCCharge トレイトにおいて、charge()必須メソッド です。つまり、実装者はすべてこのメソッドを実装しなければなりません。voltage() および charge_time()デフォルトメソッド です。これらはデフォルトの実装を提供しており、実装者はこれらをオーバーライドするか、そのまま使用するかを選択できます。Phone は charge() のみを実装し、他の 2 つのメソッドについてはデフォルトの実装を使用します。一方、Laptop は voltage() をオーバーライドし、残りのメソッドについてはデフォルトを使用します。


(2) ▶ サンプル:トレイトの派生 — デバッグ、クローン作成、および比較 (難易度 ⭐⭐)

RUST
// ============================================
// Derivable traits: Debug, Clone, Copy, PartialEq
// ============================================

// Without deriving, none of these operations would work
#[derive(Debug, Clone, Copy, PartialEq)]
struct ChargerSpec {
    brand: &'static str,
    watts: u32,
    usb_c: bool,
}

// PartialEq is needed for this custom type
#[derive(Debug, Clone, PartialEq)]
struct Device {
    name: String,
    required_watts: u32,
}

fn main() {
    // --- Debug: pretty-print with {:?} ---
    let spec1 = ChargerSpec { brand: "Anker", watts: 65, usb_c: true };
    println!("Debug: {:?}", spec1);
    println!("Pretty: {:#?}", spec1);

    // --- Clone: explicit copy ---
    let spec2 = spec1.clone();   // spec1 is still valid
    println!("Cloned: {:?}", spec2);

    // --- Copy: implicit copy (only if Copy is derived) ---
    let spec3 = spec1;            // spec1 is STILL valid because ChargerSpec is Copy!
    println!("Copied (implicit): {:?}", spec3);
    println!("Original still valid: {:?}", spec1);  // Works!

    // --- PartialEq: equality comparison ---
    let spec_a = ChargerSpec { brand: "Anker", watts: 65, usb_c: true };
    let spec_b = ChargerSpec { brand: "Anker", watts: 65, usb_c: true };
    let spec_c = ChargerSpec { brand: "Baseus", watts: 65, usb_c: true };

    println!("spec_a == spec_b: {}", spec_a == spec_b);  // true
    println!("spec_a == spec_c: {}", spec_a == spec_c);  // false (brand differs)
    println!("spec_a != spec_c: {}", spec_a != spec_c);  // true

    // --- Practical: filtering devices ---
    let phone = Device {
        name: String::from("Phone"),
        required_watts: 18,
    };
    let laptop = Device {
        name: String::from("Laptop"),
        required_watts: 65,
    };

    // Clone a device
    let phone_backup = phone.clone();
    println!("\nPhone backup: {:?}", phone_backup);

    // Compare devices by required_watts
    let charger_watts = 65;
    let compatible = vec![phone, laptop]
        .iter()
        .filter(|d| d.required_watts <= charger_watts)
        .collect::<Vec<_>>();
    println!("Compatible devices (<= {}W): {:?}", charger_watts, compatible);
}

出力:

TEXT
Debug: ChargerSpec { brand: "Anker", watts: 65, usb_c: true }
Pretty: ChargerSpec {
    brand: "Anker",
    watts: 65,
    usb_c: true,
}
Cloned: ChargerSpec { brand: "Anker", watts: 65, usb_c: true }
Copied (implicit): ChargerSpec { brand: "Anker", watts: 65, usb_c: true }
Original still valid: ChargerSpec { brand: "Anker", watts: 65, usb_c: true }
spec_a == spec_b: true
spec_a == spec_c: false
spec_a != spec_c: true

Phone backup: Device { name: "Phone", required_watts: 18 }
Compatible devices (<= 65W): [Device { name: "Phone", required_watts: 18 }, Device { name: "Laptop", required_watts: 65 }]

たった1行のコード #[derive(Debug, Clone, Copy, PartialEq)] だけで、Rust コンパイラは 4 つのトレイト ChargerSpec の実装を自動的に生成しました。CopyClone の違いに注目してください。Clone では .clone() を明示的に呼び出す必要がありますが、Copy は暗黙的なビット単位のコピーです (代入には移動は伴いません)。DeviceにはCopyがありません。これは、String型がCopyを実装していないためです(この型はヒープ上にメモリを割り当てます)。


(3) ▶ サンプル:パラメータとしてのトレイト — impl トレイトとジェネリック制約 (難易度 ⭐⭐)

RUST
// ============================================
// Trait as parameter: impl Trait, generic bounds, where clause
// ============================================

trait USBDevice {
    fn device_name(&self) -> &str;
    fn power_draw(&self) -> u32;
}

struct Mouse;
struct Keyboard;
struct Webcam;

impl USBDevice for Mouse {
    fn device_name(&self) -> &str { "Mouse" }
    fn power_draw(&self) -> u32 { 2 }
}

impl USBDevice for Keyboard {
    fn device_name(&self) -> &str { "Keyboard" }
    fn power_draw(&self) -> u32 { 3 }
}

impl USBDevice for Webcam {
    fn device_name(&self) -> &str { "Webcam" }
    fn power_draw(&self) -> u32 { 5 }
}

// --- Style 1: impl Trait (sugar for simple cases) ---
fn plug_device(device: &impl USBDevice) {
    println!("[Plugged] {} (draws {}W)", device.device_name(), device.power_draw());
}

// --- Style 2: Generic bound T: Trait (explicit type parameter) ---
fn print_device_spec<T: USBDevice>(device: &T) {
    println!("[Spec] {} - Power: {}W", device.device_name(), device.power_draw());
}

// --- Style 3: where clause (best for complex bounds) ---
fn check_compatible<T>(device: &T, max_power: u32) -> bool
where
    T: USBDevice,
{
    device.power_draw() <= max_power
}

// --- Style 4: Multiple trait bounds ---
use std::fmt::Debug;
fn describe_device(device: &(impl USBDevice + Debug)) {
    println!("[Debug] Device: {:?}", device);
}

fn main() {
    let mouse = Mouse;
    let keyboard = Keyboard;
    let webcam = Webcam;

    // impl Trait syntax
    plug_device(&mouse);
    plug_device(&keyboard);

    // Generic bound syntax
    print_device_spec(&webcam);

    // where clause
    println!("\n--- Compatibility Check (max 3W) ---");
    println!("Mouse compatible: {}", check_compatible(&mouse, 3));
    println!("Keyboard compatible: {}", check_compatible(&keyboard, 3));
    println!("Webcam compatible: {}", check_compatible(&webcam, 3));

    // Calculate total power draw for a list
    let devices: Vec<&dyn USBDevice> = vec![&mouse, &keyboard, &webcam];
    let total_power: u32 = devices.iter().map(|d| d.power_draw()).sum();
    println!("\nTotal power draw: {}W / 15W budget", total_power);
}

出力:

TEXT
[Plugged] Mouse (draws 2W)
[Plugged] Keyboard (draws 3W)
[Spec] Webcam - Power: 5W

--- Compatibility Check (max 3W) ---
Mouse compatible: true
Keyboard compatible: true
Webcam compatible: false

Total power draw: 10W / 15W budget

4つのトレイトパラメータのスタイルには、それぞれ独自のユースケースがあります:impl Trait(簡潔で、単一のトレイトに適しています)、T: Trait(明示的な型パラメータ名を使用し、参照型に適しています)、where T: Trait(複数の制約がある場合に最も可読性が高くなります)、impl Trait + AnotherTrait (複数の制約がある場合。なお、Vec<&dyn USBDevice>はトレイトオブジェクトであり(次の例を参照)、ここでは異なる型の参照を格納するために使用されています。)


(4) ▶ サンプル:dyn Trait トレイトオブジェクトとトレイトの継承 (難易度: ⭐⭐⭐)

RUST
// ============================================
// dyn Trait (runtime dispatch) + Trait inheritance
// ============================================

use std::fmt::Debug;

// --- Super trait (trait inheritance) ---
// AnyDevice "inherits" from Debug: to implement AnyDevice,
// a type must also implement Debug
trait AnyDevice: Debug {
    fn model_name(&self) -> &str;
}

// UsbDevice extends AnyDevice: it requires Debug + AnyDevice
trait UsbDevice: AnyDevice {
    fn usb_version(&self) -> &str;
    fn transfer_speed(&self) -> &str;
}

// --- Implement the trait hierarchy ---
#[derive(Debug)]
struct FlashDrive {
    name: String,
    capacity_gb: u32,
}

impl AnyDevice for FlashDrive {
    fn model_name(&self) -> &str {
        &self.name
    }
}

impl UsbDevice for FlashDrive {
    fn usb_version(&self) -> &str {
        "USB 3.2 Gen 2"
    }

    fn transfer_speed(&self) -> &str {
        "10 Gbps"
    }
}

#[derive(Debug)]
struct ExternalSSD {
    name: String,
    capacity_tb: f64,
}

impl AnyDevice for ExternalSSD {
    fn model_name(&self) -> &str {
        &self.name
    }
}

impl UsbDevice for ExternalSSD {
    fn usb_version(&self) -> &str {
        "USB 3.2 Gen 2x2"
    }

    fn transfer_speed(&self) -> &str {
        "20 Gbps"
    }
}

// --- Function using trait objects ---
// Accept a heterogeneous collection of UsbDevice implementors
fn list_devices(devices: &[Box<dyn UsbDevice>]) {
    for (i, device) in devices.iter().enumerate() {
        println!(
            "Device #{}: {} ({} - {}, Debug: {:?})",
            i + 1,
            device.model_name(),
            device.usb_version(),
            device.transfer_speed(),
            device,
        );
    }
}

// --- Function returning a trait object ---
fn make_device(device_type: &str) -> Option<Box<dyn UsbDevice>> {
    match device_type {
        "flash" => Some(Box::new(FlashDrive {
            name: String::from("SanDisk 128GB"),
            capacity_gb: 128,
        })),
        "ssd" => Some(Box::new(ExternalSSD {
            name: String::from("Samsung T7 1TB"),
            capacity_tb: 1.0,
        })),
        _ => None,
    }
}

fn main() {
    // Heterogeneous collection: different types, same trait
    let drive1 = Box::new(FlashDrive {
        name: String::from("Kingston 64GB"),
        capacity_gb: 64,
    });
    let drive2 = Box::new(ExternalSSD {
        name: String::from("WD My Passport 2TB"),
        capacity_tb: 2.0,
    });

    let all_devices: Vec<Box<dyn UsbDevice>> = vec![drive1, drive2];
    println!("--- Connected Devices ---");
    list_devices(&all_devices);

    // Factory function returning trait objects
    println!("\n--- Device Factory ---");
    if let Some(device) = make_device("ssd") {
        println!("Created: {} ({} - {})", device.model_name(), device.usb_version(), device.transfer_speed());
    }

    // Trait inheritance in action: UsbDevice requires Debug
    // so we can use both {:?} and trait methods
    println!("\n--- Debug via Super Trait ---");
    let flash = FlashDrive {
        name: String::from("Lexar 32GB"),
        capacity_gb: 32,
    };
    // flash has Debug (from AnyDevice: Debug), AnyDevice, and UsbDevice
    println!("Debug: {:?}", flash);
    println!("Model: {}", flash.model_name());
    println!("USB: {}", flash.usb_version());
}

出力:

TEXT
--- Connected Devices ---
Device #1: Kingston 64GB (USB 3.2 Gen 2 - 10 Gbps, Debug: FlashDrive { name: "Kingston 64GB", capacity_gb: 64 })
Device #2: WD My Passport 2TB (USB 3.2 Gen 2x2 - 20 Gbps, Debug: ExternalSSD { name: "WD My Passport 2TB", capacity_tb: 2.0 })

--- Device Factory ---
Created: Samsung T7 1TB (USB 3.2 Gen 2x2 - 20 Gbps)

--- Debug via Super Trait ---
Debug: FlashDrive { name: "Lexar 32GB", capacity_gb: 32 }
Model: Lexar 32GB
USB: USB 3.2 Gen 2

特性の継承(スーパートレート):trait AnyDevice: Debug は、「AnyDevice を実装する型は、Debug も実装しなければならない」ことを意味します。さらにその上に trait UsbDevice: AnyDevice が重ねられ、3階層のトレイト階層が形成されます。UsbDevice を実装する型は、Debug + AnyDevice + UsbDevice のすべてのメソッドも実装しなければなりません。

トレイトオブジェクト dyn TraitBox<dyn UsbDevice> を使用すると、同じトレイトを実装する 異なる型のオブジェクトを単一のコレクションに格納することができます。メソッドの呼び出しは、実行時に仮想テーブル(vtable)を介してディスパッチされます。これにより、わずかなパフォーマンスのオーバーヘッドが発生しますが、非常に高い柔軟性が得られます。make_device関数(Option<Box<dyn UsbDevice>>を返す)は、「ファクトリパターン」を体現しています。


(5) ▶ サンプル:題 5:総合演習――図形の面積の求め方(難易度 ⭐⭐⭐)

RUST
// ============================================
// Comprehensive Example:Trait + Generic Constraints + dyn Trait
// ============================================

use std::fmt::Debug;

trait Shape: Debug {
    fn area(&self) -> f64;
    fn name(&self) -> &str;
    fn describe(&self) -> String {
        format!("{}: Area = {:.2}", self.name(), self.area())
    }
}

#[derive(Debug)]
struct Circle { radius: f64 }
#[derive(Debug)]
struct Rectangle { width: f64, height: f64 }
#[derive(Debug)]
struct Triangle { base: f64, height: f64 }

impl Shape for Circle {
    fn area(&self) -> f64 { std::f64::consts::PI * self.radius * self.radius }
    fn name(&self) -> &str { "Circular" }
}

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

impl Shape for Triangle {
    fn area(&self) -> f64 { 0.5 * self.base * self.height }
    fn name(&self) -> &str { "Triangle" }
}

fn total_area(shapes: &[Box<dyn Shape>]) -> f64 {
    shapes.iter().map(|s| s.area()).sum()
}

fn largest<T: Shape>(shapes: &[T]) -> &T {
    shapes.iter().max_by(|a, b| a.area().partial_cmp(&b.area()).unwrap()).unwrap()
}

fn print_all(shapes: &[Box<dyn Shape>]) {
    for s in shapes {
        println!("  {}", s.describe());
    }
}

fn main() {
    let shapes_static: Vec<&dyn Shape> = vec![
        &Circle { radius: 5.0 },
        &Rectangle { width: 4.0, height: 6.0 },
        &Triangle { base: 3.0, height: 8.0 },
    ];

    println!("=== Static Reference Traversal ===");
    for s in &shapes_static {
        println!("  {}", s.describe());
    }

    let shapes_dynamic: Vec<Box<dyn Shape>> = vec![
        Box::new(Circle { radius: 10.0 }),
        Box::new(Rectangle { width: 3.0, height: 7.0 }),
        Box::new(Triangle { base: 6.0, height: 4.0 }),
    ];

    println!("\n=== Dynamic Distribution ===");
    print_all(&shapes_dynamic);
    println!("Total Area: {:.2}", total_area(&shapes_dynamic));

    let homogenous = vec![
        Circle { radius: 3.0 },
        Circle { radius: 7.0 },
        Circle { radius: 5.0 },
    ];
    let biggest = largest(&homogenous);
    println!("\nLargest Circle: {}", biggest.describe());
}

出力:

TEXT
=== Static Reference Traversal ===
  Circular: Area = 78.54
  Rectangle: Area = 24.00
  Triangle: Area = 12.00

=== Dynamic Distribution ===
  Circular: Area = 314.16
  Rectangle: Area = 21.00
  Triangle: Area = 12.00
Total Area: 347.16

Largest Circle: Circular: Area = 153.94

同じ Shape トレイトが 3 つの方法で使用されます。すなわち、静的なスライス参照には &dyn Shape、動的なコレクションの分散には Box<dyn Shape>、ジェネリック制約としての最大値の検索には T: Shape が使用されます。describe は、このトレイトのデフォルトメソッドであり、すべての実装クラスが自動的に継承します。


❓ よくある質問

Q impl Traitdyn Trait の違いは何ですか?それぞれどのような場合に使用すべきですか?
A impl Trait はコンパイル時の静的ディスパッチを使用するのに対し、dyn Trait は実行時の動的ディスパッチを使用します。
Q #[derive(Debug)]と手動実装の違いは何ですか?
A deriveは定型コードを自動生成するため、フィールド数の少ない単純な構造体に適しています。
Q CopyClone の具体的な違いは何ですか?
A Copy は暗黙的なビット単位のコピー(代入によって所有権は移転しない)であるのに対し、Clone は明示的なディープコピー(.clone() を呼び出す)です。
Q オブジェクト指向言語におけるトレイトの継承とクラスの継承の違いは何ですか?
A Rustにおけるトレイトの継承は、「インターフェースの継承」(メソッドのシグネチャを継承すること)であり、「実装の継承」(実装と状態を継承すること)ではありません。
Q トレイトの制約における T: Trait1 + Trait2where T: Trait1 + Trait2 の表記は同じものですか?
A 意味はまったく同じですが、構文のスタイルが異なります。
Q 空のトレイト(マーカー・トレイト)の目的は何ですか?
A マーカー・トレイトにはメソッドがありません。これは、特定のコンパイラの挙動や制約を有効にするために、型に「ラベル」を付けるために使用されます。

📖 まとめ


📝 練習問題

  1. 難易度 ⭐: trait Drawable { fn draw(&self); } を定義してください。2つの構造体 CircleSquare に対してこのトレイトを実装し、それぞれが異なる図形を出力するようにしてください。また、draw を呼び出すジェネリック関数 fn render<T: Drawable>(item: &T) を記述してください。main 関数内で、それぞれ円と正方形を描画してください。

  2. 難易度 ⭐⭐: trait Summary { fn summarize(&self) -> String; fn author(&self) -> &str; } というトレイトを定義してください。ここで、author()"Anonymous" を返すデフォルトメソッドです。このトレイトを、構造体 Article { title: String, content: String } および Tweet { username: String, text: String } に対して実装してください。2つの記事と2つのツイートを作成し、それらをVec<Box<dyn Summary>>に格納して、それらを反復処理し、要約を出力してください。

  3. 難易度 ⭐⭐⭐: 特性の階層構造を定義してください:trait Vehicle: std::fmt::Debug { fn fuel_type(&self) -> &str; }、次にtrait ElectricVehicle: Vehicle { fn battery_capacity_kwh(&self) -> f64; fn range_km(&self) -> f64; }TeslaModel3およびNissanLeafという構造体に対してElectricVehicleを実装してください。各車の情報を順に巡回して出力する関数 fn print_fleet(vehicles: &[Box<dyn ElectricVehicle>]) を作成してください。main関数内で、両方の車種のインスタンスを作成し、テストを行ってください。

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%