Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

列挙型とパターンマッチング

複数の選択肢を表す「列挙型」と、それを扱う「パターンマッチング」を学びます。

列挙型(Enum)とは

列挙型は複数の選択肢のうち1つを表す型です。

enum Direction {
    North,
    South,
    East,
    West,
}

fn main() {
    let dir = Direction::North;
}

列挙型の定義と使用

基本的な列挙型

enum Status {
    Pending,
    InProgress,
    Completed,
    Cancelled,
}

fn main() {
    let task_status = Status::InProgress;
}

データを持つ列挙型

各バリアントがデータを持てます。

enum Message {
    Quit,                       // データなし
    Move { x: i32, y: i32 },    // 構造体風
    Write(String),              // 単一の値
    ChangeColor(u8, u8, u8),    // タプル風
}

fn main() {
    let msg1 = Message::Quit;
    let msg2 = Message::Move { x: 10, y: 20 };
    let msg3 = Message::Write(String::from("Hello"));
    let msg4 = Message::ChangeColor(255, 0, 0);
}

match式

matchは列挙型のすべてのバリアントを処理します。

#![allow(unused)]
fn main() {
enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter,
}

fn value_in_cents(coin: Coin) -> u32 {
    match coin {
        Coin::Penny => 1,
        Coin::Nickel => 5,
        Coin::Dime => 10,
        Coin::Quarter => 25,
    }
}
}

matchは網羅的

すべてのバリアントを扱わないとエラーになります。

#![allow(unused)]
fn main() {
fn value_in_cents(coin: Coin) -> u32 {
    match coin {
        Coin::Penny => 1,
        // エラー!他のバリアントが抜けている
    }
}
}

データの取り出し

#![allow(unused)]
fn main() {
enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
}

fn process(msg: Message) {
    match msg {
        Message::Quit => {
            println!("終了します");
        }
        Message::Move { x, y } => {
            println!("移動: ({}, {})", x, y);
        }
        Message::Write(text) => {
            println!("メッセージ: {}", text);
        }
    }
}
}

_ プレースホルダ

残りすべてにマッチします。

#![allow(unused)]
fn main() {
fn describe_number(n: i32) {
    match n {
        1 => println!("一"),
        2 => println!("二"),
        3 => println!("三"),
        _ => println!("その他"),  // 1, 2, 3以外すべて
    }
}
}

Option型(復習と深掘り)

Optionは「値があるかないか」を表す標準の列挙型です。

#![allow(unused)]
fn main() {
enum Option<T> {
    Some(T),
    None,
}
}

Optionの使用

fn find_user(id: u32) -> Option<String> {
    if id == 1 {
        Some(String::from("太郎"))
    } else {
        None
    }
}

fn main() {
    match find_user(1) {
        Some(name) => println!("見つかりました: {}", name),
        None => println!("見つかりませんでした"),
    }
}

Result型(復習と深掘り)

Resultは「成功か失敗か」を表す標準の列挙型です。

#![allow(unused)]
fn main() {
enum Result<T, E> {
    Ok(T),
    Err(E),
}
}

Resultの使用

fn divide(a: f64, b: f64) -> Result<f64, String> {
    if b == 0.0 {
        Err(String::from("ゼロ除算エラー"))
    } else {
        Ok(a / b)
    }
}

fn main() {
    match divide(10.0, 2.0) {
        Ok(result) => println!("結果: {}", result),
        Err(e) => println!("エラー: {}", e),
    }
}

if let 式

特定のパターンだけ処理したい場合に便利です。

fn main() {
    let some_value = Some(5);

    // matchを使う場合
    match some_value {
        Some(n) => println!("値: {}", n),
        None => (),  // 何もしない
    }

    // if letを使う場合(簡潔)
    if let Some(n) = some_value {
        println!("値: {}", n);
    }
}

if let else

fn main() {
    let coin = Coin::Quarter;

    if let Coin::Quarter = coin {
        println!("25セント!");
    } else {
        println!("25セントではない");
    }
}

while let 式

パターンがマッチする間ループします。

fn main() {
    let mut stack = vec![1, 2, 3];

    while let Some(top) = stack.pop() {
        println!("{}", top);
    }
}
// 出力: 3, 2, 1

列挙型のメソッド

列挙型にもメソッドを定義できます。

enum Status {
    Pending,
    InProgress,
    Completed,
}

impl Status {
    fn description(&self) -> &str {
        match self {
            Status::Pending => "未着手",
            Status::InProgress => "進行中",
            Status::Completed => "完了",
        }
    }

    fn is_done(&self) -> bool {
        matches!(self, Status::Completed)
    }
}

fn main() {
    let status = Status::InProgress;
    println!("{}", status.description());
    println!("完了?: {}", status.is_done());
}

実践例: 状態管理

#![allow(unused)]
fn main() {
#[derive(Debug)]
enum TaskState {
    Todo,
    InProgress { progress: u8 },
    Done { completed_at: String },
}

struct Task {
    name: String,
    state: TaskState,
}

impl Task {
    fn new(name: String) -> Task {
        Task {
            name,
            state: TaskState::Todo,
        }
    }

    fn start(&mut self) {
        self.state = TaskState::InProgress { progress: 0 };
    }

    fn update_progress(&mut self, progress: u8) {
        if let TaskState::InProgress { progress: ref mut p } = self.state {
            *p = progress;
        }
    }

    fn complete(&mut self, completed_at: String) {
        self.state = TaskState::Done { completed_at };
    }
}
}

まとめ

概念説明
列挙型複数の選択肢のうち1つを表す
バリアント列挙型の各選択肢
match網羅的なパターンマッチング
if let特定パターンのみ処理
Option値の有無を表す
Result成功/失敗を表す

確認テスト

Q1. match式の特徴として正しいのは?

Q2. if letを使うべき場面は?

Q3. 以下のコードの出力は?
let light = Light::Yellow; let action = match light { Light::Red => "止まれ", Light::Yellow => "注意", Light::Green => "進め" }; println!("{}", action);

Q4. 以下のコードがコンパイルエラーになる理由は?
enum Color { Red, Green, Blue } fn describe(c: Color) -> &str { match c { Color::Red => "赤", Color::Green => "緑" } }

Q5. Option<T>Noneは何を表す?


次のドキュメント: 03_traits.md