Caution

お使いのブラウザはJavaScriptが実行できない状態になっております。
当サイトはWebプログラミングの情報サイトの為、
JavaScriptが実行できない環境では正しいコンテンツが提供出来ません。
JavaScriptが実行可能な状態でご閲覧頂くようお願い申し上げます。

Rust辞典

  1. トップページ
  2. Rust辞典
  3. impl enum / match でのパターン分岐

impl enum / match でのパターン分岐

Rustのenumには『impl』でメソッドを追加でき、『match』式で全バリアントを網羅的に処理できます。コンパイラが未処理のバリアントを検出するため、安全なパターンマッチが可能です。

構文
enum Shape {
    Circle(f64),
    Rectangle(f64, f64),
    Triangle(f64, f64, f64),
}

impl Shape {
    fn area(&self) -> f64 {
        match self {
            Shape::Circle(r) => std::f64::consts::PI * r * r,
            Shape::Rectangle(w, h) => w * h,
            Shape::Triangle(a, b, c) => {
                let s = (a + b + c) / 2.0;
                (s * (s - a) * (s - b) * (s - c)).sqrt()  // ヘロンの公式
            }
        }
    }
}

// if let: 1つのバリアントだけ処理します。
if let Shape::Circle(r) = shape {
    println!("円の半径: {}", r);
}
match・if let の使い方
構文概要
match val { Variant(x) => ... }全バリアントを網羅的に処理します(漏れがあるとコンパイルエラー)。
_ => ...ワイルドカード: 残りのバリアントをまとめて処理します。
if let Variant(x) = val { ... }1つのバリアントだけ処理します。elseも使えます。
while let Variant(x) = val { ... }条件が一致する間ループします。
Variant(x) if x > 0 => ...matchのガード条件: バリアントマッチ後に追加条件を指定します。
Variant(x) | Variant2(x) => ...複数のバリアントを | でまとめて処理します。
impl Enum { fn method(&self) }enumにメソッドを追加します(structと同様)。
サンプルコード
#[derive(Debug)]
enum TrafficLight {
    Red,
    Yellow,
    Green,
}

impl TrafficLight {
    fn duration_secs(&self) -> u32 {
        match self {
            TrafficLight::Red => 60,
            TrafficLight::Yellow => 5,
            TrafficLight::Green => 45,
        }
    }

    fn can_go(&self) -> bool {
        matches!(self, TrafficLight::Green)
    }

    fn next(&self) -> TrafficLight {
        match self {
            TrafficLight::Red => TrafficLight::Green,
            TrafficLight::Green => TrafficLight::Yellow,
            TrafficLight::Yellow => TrafficLight::Red,
        }
    }
}

#[derive(Debug)]
enum Expr {
    Number(f64),
    Add(Box<Expr>, Box<Expr>),
    Mul(Box<Expr>, Box<Expr>),
    Neg(Box<Expr>),
}

impl Expr {
    fn eval(&self) -> f64 {
        match self {
            Expr::Number(n) => *n,
            Expr::Add(a, b) => a.eval() + b.eval(),
            Expr::Mul(a, b) => a.eval() * b.eval(),
            Expr::Neg(e) => -e.eval(),
        }
    }
}

fn main() {
    // enumのメソッドを使います。
    let light = TrafficLight::Red;
    println!("{:?}: {}秒, 進める?: {}", light, light.duration_secs(), light.can_go());

    let next = light.next();
    println!("次は: {:?}", next);

    // if let で1つのバリアントだけ処理します。
    let value = Some(42);
    if let Some(n) = value {
        println!("値: {}", n);
    }

    // matchのガード条件です。
    let numbers = vec![1, -3, 5, -2, 8];
    for &n in &numbers {
        match n {
            x if x > 0 => print!("正:{} ", x),
            x if x < 0 => print!("負:{} ", x),
            _ => print!("ゼロ "),
        }
    }
    println!();

    // 再帰的なenumで数式を評価します。
    // (3 + 4) * (-2) = -14
    let expr = Expr::Mul(
        Box::new(Expr::Add(
            Box::new(Expr::Number(3.0)),
            Box::new(Expr::Number(4.0)),
        )),
        Box::new(Expr::Neg(Box::new(Expr::Number(2.0)))),
    );
    println!("(3 + 4) * (-2) = {}", expr.eval());
}
概要

Rustの『match』式は全バリアントを網羅することが必須です。新しいバリアントを追加するとコンパイルエラーになるため、処理漏れを防げます。ワイルドカード(『_』)を使えばまとめて処理できます。

再帰的なenumはコンパイラが自動でサイズを決めようとしてエラーになります。そのため『Box<Expr>』のようにポインタでラップしてサイズを固定する必要があります。

enumの定義とバリアントの種類は『enum / バリアント定義』を参照してください。

記事の間違いや著作権の侵害等ございましたらお手数ですがまでご連絡頂ければ幸いです。