emahiro/b.log

Drastically Repeat Yourself !!!!

Rust のパターン記法 ( Pattern matching )

Overview

Rust の Pattern matching がとても便利だなと思ったので備忘録です。

TRPL には以下のように記載されています。

doc.rust-jp.rs

これを読めばパターン記法に関して必要なことは大体網羅されてるので本当にドキュメントが充実してるいい環境だなと思います。Rust。

単純なパターンマッチング

let x = 10;
match x {
    1 => print!("one\n"),
    2 => print!("two\n"),
    _ => print!("none\n")
};

switch ケースの default 的などこにもマッチしなかった場合には match 記法では _ で表現します。
わかりやすいなーと思います。

Some/None というパターンマッチング

個人的にいいな思ったパターンマッチングのデザインがこれです。

let x: Option<i32> = Some(10);
match x {
    Some(n) => print!("found {}\n", n),
    None => print!("none")
};

サンプルは上記の通りなんですが、パターンマッチさせる対象の型がはっきりしない場合(= Option 型の場合)に match 記法を使うとエディターの補完サポートなどもあって、マッチして取り出した型がなんなのかがわかります。

これで実装するときに有用だなと思ったのが、コマンドラインツールを実装するときで、cat コマンドを自作するときに引数の2つ目(index=1) に何が入るのかを取り出すのに使えます。

use std::{env::args, fs::read_to_string};

fn main() {
    match args().nth(1) {
        Some(path) => print!("path is {}\n", path),
        None => print!("No path is specified\n")
    }
}

サンプルコードは上記です。実行するとこんな感じで指定したファイルを標準出力に吐きます。

cargo run ./src/main.rs
   Compiling rs_cathandson v0.1.0 ($HOME/src/github.com/emahiro/il/rs_sandbox/rs_cat_handson)
    Finished dev [unoptimized + debuginfo] target(s) in 0.39s
     Running `target/debug/rs_cathandson ./src/main.rs`
use std::{env::args, fs::read_to_string};

fn run_cat(path: String) {
    match read_to_string(path) {
        Ok(content) => print!("{}\n", content),
        Err (err) => print!("error: {}\n", err)
    };
}

fn main() {
    match args().nth(1) {
        Some(path) => run_cat(path),
        None => print!("No path is specified\n")
    }
}

if let 記法

Option 型は値がない可能性があることを表す型で Some で値を取り出し None は値がなかったことになります。
パターンマッチングにおいて値があるときだけ処理を続けたい、というケースを実装する場合 None が冗長だったりします。そこで使うのが if let です。

fn main() {
    if let Some(path) = args().nth(1) {
        run_cat(path)
    }
}

シンプルですね。ただこれだけで match Some/None を表現してるかと思うと表現力の高い言語だなーと思います。

タプルのパターンマッチング

パターンマッチングしたいターゲットが2つある場合タプルが使えます。サンプルは以下です。match 記法に対して (A, B) というタプルを取ることができます。

let arg1 = args().nth(1);
let arg2 = args().nth(2);

match (arg1, arg2) {
    (Some(arg1), Some(arg2)) => println!("arg1: {}, arg2: {}", arg1, arg2),
    _ => println!("Pattern or path is not specified.\n"),
};

この場合 None のとりうる値が増えます。上記のサンプルでは default を表現するために _ を使ってますが、(Some(A), Some(B)) のようにタプルでパターンマッチをかける場合、以下のように Some に対応した None をそれぞれカバーしないとコンパイラに怒られます。

non-exhaustive patterns: `(None, Some(_))` and `(Some(_), None)` not covered patterns `(None, Some(_))` and `(Some(_), None)` not covered
  1. Some(A), Some(B)
  2. None, Some(_)
  3. Some(_), None
  4. None, None

_ は 2,3,4 全部のケースを1つにまとめることができる記法です。

まとめ

Rust をぼちぼち書き始めてますが、パターンマッチやResult型でのエラーハンドリングなど普段使ってる Go にはない要素が多数あって描いてて新鮮です。