18. 発展トピック

🎯 この章のゴール

  • トレイトオブジェクトで動的ディスパッチを使う
  • unsafe・マクロ・高度な型の存在を知る
  • 次に何を学ぶかの地図を手に入れる

💡 ひとことで言うと

ここまでで Rust の土台は完成です 🎉 この章は「こんな道具もあるよ」という 道具箱のカタログ。必要になったときに思い出せれば十分です。

トレイトオブジェクト(dyn Trait)

trait Shape {
    fn area(&self) -> f64;
    fn name(&self) -> String {
        String::from("図形")
    }
}

struct Circle(f64);
struct Square(f64);

impl Shape for Circle {
    fn area(&self) -> f64 { 3.14 * self.0 * self.0 }
    fn name(&self) -> String { String::from("円") }
}

impl Shape for Square {
    fn area(&self) -> f64 { self.0 * self.0 }
}

fn main() {
    let shapes: Vec<Box<dyn Shape>> = vec![Box::new(Circle(1.0)), Box::new(Square(2.0))];
    for s in &shapes {
        println!("{}: {:.2}", s.name(), s.area());
    }
}
Playground で開く ↗
ジェネリクス(impl Trait / T: Trait)トレイトオブジェクト(dyn Trait)
決定タイミングコンパイル時(静的ディスパッチ)実行時(動的ディスパッチ)
速度最速(インライン化可能)わずかにオーバーヘッド
異なる型を混ぜる❌✅

パターンいろいろ

struct Point3 {
    x: i32,
    y: i32,
    z: i32,
}

fn main() {
    let p = Point3 { x: 1, y: -2, z: 3 };
    let Point3 { x, .. } = p;                 // 一部だけ分解
    match p.y {
        n @ -5..=-1 => println!("小さな負の数 {n}"), // @ で束縛しつつ範囲チェック
        _ => println!("その他"),
    }
    let nums = [1, 2, 3, 4, 5];
    if let [first, .., last] = nums {          // スライスパターン
        println!("{x} {first} {last} {}", p.z);
    }
}
Playground で開く ↗

unsafe Rust

コンパイラが安全性を証明できない操作を「自分が責任を持つ」と宣言して行います。

fn main() {
    let mut num = 5;
    let r = &raw mut num;  // 生ポインタの作成は安全
    unsafe {
        *r += 1;           // 参照外しは unsafe
    }
    println!("{num}");
}
Playground で開く ↗

許可される操作: 生ポインタの参照外し / unsafe fn の呼び出し / 可変 static へのアクセス / unsafe trait の実装 / union のフィールドアクセス。 実際には 安全な API の内側に unsafe を閉じ込める のが Rust 流です(Vec の内部実装など)。

マクロ

macro_rules! hashmap {
    ($($k:expr => $v:expr),* $(,)?) => {{
        let mut m = std::collections::HashMap::new();
        $( m.insert($k, $v); )*
        m
    }};
}

fn main() {
    let ages = hashmap! { "ferris" => 8, "corro" => 3 };
    println!("{:?}", ages.get("ferris"));
}
Playground で開く ↗
  • 宣言的マクロ macro_rules!: パターンマッチでコードを生成
  • 手続き的マクロ: #[derive(...)]、属性マクロ(#[tokio::main])、関数風マクロ。別クレートで定義

高度な型・トレイト(さわりだけ)

use std::ops::Add;

#[derive(Debug, Clone, Copy, PartialEq)]
struct Meters(f64);

impl Add for Meters {          // 演算子オーバーロード
    type Output = Meters;      // 関連型
    fn add(self, other: Meters) -> Meters {
        Meters(self.0 + other.0)
    }
}

type Callback = Box<dyn Fn(i32) -> i32>; // 型エイリアス

fn never_returns() -> ! {      // never 型: 決して戻らない
    panic!("ここで終わり");
}

fn main() {
    let total = Meters(1.5) + Meters(2.0);
    let double: Callback = Box::new(|x| x * 2);
    println!("{total:?} {}", double(21));
    if total.0 < 0.0 {
        never_returns();
    }
}
Playground で開く ↗

次のステップ 🗺️

やりたいことおすすめ
もっと深くThe Book 日本語版 の 19〜21 章(最終プロジェクト: マルチスレッド Web サーバー)
例で学ぶRust by Example
手を動かすRustlings(小さな演習問題集)
CLI ツールclap, anyhow, serde
Webaxum, tokio, sqlx
unsafe の深淵The Rustonomicon

📝 まとめ

  • 異なる型をまとめて扱うなら Box<dyn Trait>
  • unsafe は限定された操作だけを許可し、安全な API で包んで使う
  • マクロでコード生成、トレイトで演算子オーバーロード

✅ 理解度クイズ

Q1
異なる型(Circle, Square)を 1 つの Vec に入れて、共通のメソッドを呼びたい。適切な型は?
Q2
unsafe ブロックについて正しいものは?
Q3
macro_rules! で定義する宣言的マクロの特徴は?
Q4
type Km = i32; のような型エイリアスと、struct Km(i32);(newtype)の違いは?

🎉 完走おめでとうございます! トップページ で全章のクイズ結果を確認してみましょう。