Skip to content

Instantly share code, notes, and snippets.

@omasanori
Last active Mar 27, 2022
Embed
What would you like to do?
Rustのパターンマッチの話

Rustのパターンマッチの話

既に穴だらけですが、やれるだけやっていきます。今回はパターンマッチの話です。

TL;DR(経験者向け)

はい、あなたのよく知るパターンマッチです。ガードも使えますが、他の言語でガードを使うパターンの一部はOCamlのorパターンと同等の機能や範囲を表す機能によって置き換えることができます。

パターンマッチとは

パターンマッチは値、特に列や木のような構造を持つ値の中に特定のパターンに合致する部分があるかどうかを探し、また合致した部分を取り出すことです。例えば、grepコマンドはファイルの内容から正規言語(やその拡張)で表現できるパターンに合致する文字列を探し、取り出すことができます。よってgrepコマンドはパターンマッチツールの一種といえます。

いくつかのプログラミング言語は単純なif文によるガードの他にパターンマッチを含む分岐をサポートしています。Rustもその一つです。

単純なパターンマッチ

まず、パターンマッチの基本的な例を紹介します。

fn foo() -> uint {
    // ...
}

let x: uint = foo();
match x {
    0 => println!("nothing"),
    1 => println!("only one"),
    _ => println!("many")
}

これはxの値が0、1、その他の値のいずれであるかによって表示する値を変化させています。Rustではmatch[1]でパターンマッチによる分岐を行います。_はその部分に当てはまる具体的な値は重要でなく、結果の式の中で使用することもないということを示す記号です。_はRustの他の部分でも「そこに当てはまる値を使用しない」という意図を示す記号として利用されます。

次に、xの値を利用する方法を紹介します。

match x {
    0 => println!("nothing"),
    1 => println!("only one"),
    n => println!("{} things", n)
}

この場合、0と1以外の値の場合はprintln!は第一引数の{}の位置にnの値を埋め込んで表示します。nのパターンで結果の式にnを使わなかった場合はコンパイラが知らせてくれるので、_で十分な場合に無駄に変数束縛を作る心配はありません。

ガードを含むパターンマッチ

パターンを定数で表現できる場合にmatchが有用だということは既に示しましたが、値がある条件に合致するパターンを表現するにはどうしたらよいでしょうか。結果の式でif式を使うことでも実現できますが、ガードという機能を使うことでも表現できます。

match x {
    0 => println!("zero"),
    1 => println!("one"),
    n => if is_prime(n) {
             println!("prime number {}", n);
         } else {
             println!("composite number {}", n);
         }
}

match x {
    0 => println!("zero"),
    1 => println!("one"),
    n if is_prime(n) => println!("prime number {}", n),
    n => println!("composite number {}", n)
}

どちらがより分かりやすいかは個人差もあるかと思いますが、私はガードを使って=>の左側に条件をまとめた方がより好ましいと感じます。

orパターン

では、複数のパターンで同じ結果の式を使う場合はどうしたらよいでしょうか。そんな時は|で複数のパターンを区切って=>の左側に置きましょう。

match x {
    0 | 1 => println!("It is not a prime number."),
    n => println!("The number {} might be a prime number.", n)
}

範囲でパターンマッチ

ある範囲に収まる値にマッチしたい場合はどうしたらよいでしょうか。型によっては..が使えます。

match x {
    0 .. 9 => println!("small"),
    _ => println!("large")
}

構造を持つ型とパターンマッチ

これまでは整数を扱ってきましたが、もちろんタプルのような複雑な型[2]の値に対してもパターンマッチできます。

fn bar() ~> (int, int) { /* ... */ }

let y: (int, int) = bar();

match y {
    (0, 0) => println!("origin"),
    (a, b) => println!("point ({}, {})", a, b)
}

おわりに

Rustのmatchはある種の分岐を簡潔に書くために使うことができます。

普段、C言語的なswitchではうまく表現できずにif の行列を使ったりdefault:の中で分岐したりしていませんか。そんなあなたに必要なものは、ひょっとするとパターンマッチかもしれません。言語を選択する余地があるのなら、パターンマッチのあるプログラミング言語を使ってみませんか。たとえ言語機能として組み込んでいなくても、ライブラリでうまくサポートしているかもしれません。

パターンマッチのあるプログラミング言語の中でどれを選んだらいいか迷っているそこのあなた。Rustを使ってみませんか。

[1]Rustでは宣言を除く構文のほとんどが式として定義されている。matchを変数宣言の右辺や関数呼び出しの実引数を渡す箇所に置くこともでき、(きっと)あなたが期待した通りに動作する。
[2]先にどのような型があるかを紹介する予定だったのですが、まだ書いていません。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment