Rustのトレイト:共通の挙動の定義
トレイトは、Rustにおける「インターフェース」に相当するものです。トレイトは一連のメソッドのシグネチャを定義し、異なる型が同じトレイトを実装することで、同じ動作契約を共有することができます。
具体的なタイプが「そのものが何であるか」を表すのに対し、特性は「そのものが何ができるか」を表します。例えばUSB-Cポートを例に挙げましょう。スマートフォン、ノートパソコン、タブレットのいずれであっても、USB-Cに対応していれば、接続して充電することができます。これらのデバイスの内部構造は大きく異なるかもしれませんが、ユーザーに約束される動作は一貫しています。
1. 学習内容
- トレイト
trait Name { fn method(&self); }を定義し、型に対してトレイトを実装するための構文 - デフォルトメソッド — トレイトの定義内でデフォルトの実装を指定する。実装側は、これをオーバーライドするかどうかの選択が可能である
- 派生特性:
#[derive(Debug, Clone, Copy, PartialEq)] - パラメータとしての特性:
impl Trait構文とジェネリック制約T: Trait - 特性オブジェクト
dyn Trait—実行時の動的割り当て - トレイトの継承(スーパートレイト):あるトレイトが別のトレイトからメソッドを継承する
2. 概念図
以下のマーメイド図は、ある特性に関する関係性の完全な連鎖――その定義から、さまざまな型に対する実装、そして最終的にはポリモーフィックな呼び出しに至るまで――を示しています。
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)は、ある電子機器メーカーの設計者です。同社の製品ラインナップは、スマートフォン、ノートパソコン、タブレットへと拡大しており、各デバイスにはそれぞれ独自の充電ロジックが採用されています。
当初、彼はデバイスごとに個別に充電コードを書いていました:
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) より複雑な要件:バッチ充電
プロダクトマネージャーは、複数の異なる種類のデバイスを同時に充電できる「ユニバーサル充電ステーション」の開発を依頼しました:
// This code cannot be compiled.——Unknown parameter type
// fn charge_all_devices(devices: ???) {
// for device in devices {
// device.charge();
// }
// }
トレイトがないと、charge_all_devicesは型が混在するコレクションを受け入れることができません。各型ごとに関数をオーバーロードするか、あるいは諦めるしかありません。
(3) 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);
}
出力:
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) 特性システムの概要
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) ▶ サンプル:トレイトの定義とデフォルトメソッド — デバイス充電器 (難易度 ⭐)
// ============================================
// 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));
}
出力:
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) ▶ サンプル:トレイトの派生 — デバッグ、クローン作成、および比較 (難易度 ⭐⭐)
// ============================================
// 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);
}
出力:
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の実装を自動的に生成しました。CopyとCloneの違いに注目してください。Cloneでは.clone()を明示的に呼び出す必要がありますが、Copyは暗黙的なビット単位のコピーです (代入には移動は伴いません)。DeviceにはCopyがありません。これは、String型がCopyを実装していないためです(この型はヒープ上にメモリを割り当てます)。
(3) ▶ サンプル:パラメータとしてのトレイト — impl トレイトとジェネリック制約 (難易度 ⭐⭐)
// ============================================
// 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);
}
出力:
[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 トレイトオブジェクトとトレイトの継承 (難易度: ⭐⭐⭐)
// ============================================
// 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());
}
出力:
--- 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 Trait:Box<dyn UsbDevice>を使用すると、同じトレイトを実装する 異なる型のオブジェクトを単一のコレクションに格納することができます。メソッドの呼び出しは、実行時に仮想テーブル(vtable)を介してディスパッチされます。これにより、わずかなパフォーマンスのオーバーヘッドが発生しますが、非常に高い柔軟性が得られます。make_device関数(Option<Box<dyn UsbDevice>>を返す)は、「ファクトリパターン」を体現しています。
(5) ▶ サンプル:題 5:総合演習――図形の面積の求め方(難易度 ⭐⭐⭐)
// ============================================
// 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());
}
出力:
=== 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は、このトレイトのデフォルトメソッドであり、すべての実装クラスが自動的に継承します。
❓ よくある質問
impl Trait と dyn Trait の違いは何ですか?それぞれどのような場合に使用すべきですか?impl Trait はコンパイル時の静的ディスパッチを使用するのに対し、dyn Trait は実行時の動的ディスパッチを使用します。#[derive(Debug)]と手動実装の違いは何ですか?deriveは定型コードを自動生成するため、フィールド数の少ない単純な構造体に適しています。Copy と Clone の具体的な違いは何ですか?Copy は暗黙的なビット単位のコピー(代入によって所有権は移転しない)であるのに対し、Clone は明示的なディープコピー(.clone() を呼び出す)です。T: Trait1 + Trait2 と where T: Trait1 + Trait2 の表記は同じものですか?📖 まとめ
- 特性定義
trait Name { fn method(&self); }は一連の挙動契約を宣言し、impl ブロックは特定の型に対してこれらの契約の具体的な実装を提供します。 - デフォルトメソッド は、トレイト内でデフォルトの実装を提供します。実装側は、これらをオーバーライドするか、そのまま使用するかを選択できます。
- 派生トレイト
#[derive(Debug, Clone, Copy, PartialEq)]により、コンパイラは一般的なトレイトの実装を自動的に生成できるようになります。つまり、コストをかけずに抽象化を実現できるのです。 impl Trait(静的配布)とdyn Trait(動的配布)は、トレイトを使用する2つの方法です。前者はオーバーヘッドがゼロであるのに対し、後者はより高い柔軟性を提供します- 形質の継承
trait A: SuperTrait形質の階層構造の確立――実装クラスは、そのスーパートラートのすべての制約を同時に満たさなければならない - トレイトは、Rustにおいてゼロコストの抽象化を実現するための中核となるメカニズムです。これらは、仮想関数によるオーバーヘッドのない「インターフェース」(ジェネリック)であり、必要に応じてオプションの実行時ポリモーフィズム(トレイトオブジェクト)もサポートしています。
📝 練習問題
-
難易度 ⭐:
trait Drawable { fn draw(&self); }を定義してください。2つの構造体CircleとSquareに対してこのトレイトを実装し、それぞれが異なる図形を出力するようにしてください。また、drawを呼び出すジェネリック関数fn render<T: Drawable>(item: &T)を記述してください。main 関数内で、それぞれ円と正方形を描画してください。 -
難易度 ⭐⭐:
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>>に格納して、それらを反復処理し、要約を出力してください。 -
難易度 ⭐⭐⭐: 特性の階層構造を定義してください:
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関数内で、両方の車種のインスタンスを作成し、テストを行ってください。



