やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

Rustパターン(記法)

 すべての記法を網羅する。

成果物

参考

パターンの記法一覧

  • match: リテラル
  • match: 名前付き変数
  • match: |
  • match: ...
  • 分配: struct
  • 分配: enum
  • 分配: 参照
  • _: パターン無視
  • _: 未使用ワーニング無視(変数名のプレフィクス)
  • ..: 残りを無視
  • ref, ref mut: パターンに参照を生成する
  • match: マッチガード match if
  • @束縛

match: リテラル

fn main() {
    let x = 1;
    match x {
        1 => println!("one"),
        2 => println!("two"),
        3 => println!("three"),
        _ => println!("anything"),
    }
}
$ rustc main.rs
$ ./main
one

match: 名前付き変数

 リテラルで使う。(_5,6以外のときすべて)

fn main() {
    let x = Some(5);
    let y = 10;
    match x {
        Some(5) => println!("Got 5"),
        Some(6) => println!("Got 6"),
        _ => println!("Default case, x = {:?}", x),
    }
}
$ ./main
Got 5

 名前付き変数にすると同名の変数を覆い隠してしまう(シャドーイング)。パターンをSome(y) =>としたとき、そのmatch式のアーム=> println!("Matched, y = {:?}", y),で参照できるyは新たな変数である。つまりSomeunwrap()した値となる。だが、コードを見てみると、match式の前で宣言されたlet y = 10;yがある。これが参照できなくなってしまう。その証拠に、以下match式で表示されるyの値は10でなく5である。

fn main() {
    let x = Some(5);
    let y = 10;
    match x {
        Some(y) => println!("Matched, y = {:?}", y),
        _ => println!("Default case, x = {:?}", x),
    }
    println!("at the end: x = {:?}, y = {:?}", x, y);
}
$ rustc main.rs
$ ./main
Matched, y = 5
at the end: x = Some(5), y = 10

 シャドーイングでなく、外側のyを使いたいなら、後述のマッチガードを使う。

match: |

fn main() {
    let x = 1;
    match x {
        1 | 2 => println!("one or two"),
        3 => println!("three"),
        _ => println!("anything"),
    }
}
$ rustc main.rs
$ ./main
one or two

 |orの意味。1 | 2なら、1または2の場合にマッチする。

match: ...

fn main() {
    let x = 5;
    match x {
        1 ... 5 => println!("one through five"),
        _ => println!("something else"),
    }
}
$ rustc main.rs
$ ./main
one through five

 1 ... 5は、15の範囲ならマッチする。

  • 範囲...は数値または文字のみで使える

分配: struct

struct Point {
    x: i32,
    y: i32,
}
fn main() {
    let p = Point { x: 0, y: 7 };
    let Point { x: a, y: b } = p;
    assert_eq!(0, a);
    assert_eq!(7, b);
}
  • let 構造体名 { フィールド名: 変数名 } = 構造体インスタンス;

 a, bが新しい変数。構造体のフィールドに対応させて変数名を指定してやることで、各変数に各フィールド値を代入する。

 しかし、わざわざフィールド名と異なる名前をつけるのも面倒。フィールド名を変数名として使いまわすときの略記がある。以下。

struct Point {
    x: i32,
    y: i32,
}
fn main() {
    let p = Point { x: 0, y: 7 };
    let Point { x, y } = p;
    assert_eq!(0, x);
    assert_eq!(7, y);
}

 一部にのみ分配する方法もある。

struct Point {
    x: i32,
    y: i32,
}
fn main() {
    let p = Point { x: 0, y: 7 };
    match p {
        Point { x, y: 0 } => println!("On the x axis at {}", x),
        Point { x: 0, y } => println!("On the y axis at {}", y),
        Point { x, y } => println!("On neither axis: ({}, {})", x, y),
    }
}
$ rustc main.rs
$ ./main
On the y axis at 7

分配: enum

enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}
fn main() {
    let msg = Message::ChangeColor(0, 160, 255);
    match msg {
        Message::Quit => {
            println!("The Quit variant has no data to destructure.")
        },
        Message::Move { x, y } => {
            println!(
                "Move in the x direction {} and in the y direction {}",
                x,
                y
            );
        }
        Message::Write(text) => println!("Text message: {}", text),
        Message::ChangeColor(r, g, b) => {
            println!(
                "Change the color to red {}, green {}, and blue {}",
                r,
                g,
                b
            )
        }
    }
}
$ rustc main.rs
...
$ ./main
Change the color to red 0, green 160, and blue 255
  • match式では全列挙子を網羅しないとエラーになる
  • 構造体なら持っているフィールドは名前付き変数にできる
    • 定義と同じ名前にすれば略記できる
  • タプル構造体なら変数の数が同じであるべき

分配: 参照

struct Point {
    x: i32,
    y: i32,
}
fn main() {
    let points = vec![
        Point { x: 0, y: 0 },
        Point { x: 1, y: 5 },
        Point { x: 10, y: -3 },
    ];
    let sum_of_squares: i32 = points
        .iter()
        .map(|&Point { x, y }| x * x + y * y)
        .sum();
}
$ rustc main.rs
$ ./main
On the y axis at 7

 もし.map(|&Point { x, y }| x * x + y * y)&PointでなくPointならエラーになっていた。参照でも値と同じように分配できる。

 さらに複雑なパターンとして以下。タプルと構造体を含んだタプル。

let ((feet, inches), Point {x, y}) = ((3, 10), Point { x: 3, y: -10 });

_: パターン無視

 _match式の最後のアームとして役立つ。が、他のあらゆるパターンでも使える。

fn foo(_: i32, y: i32) {
    println!("このコードは引数yを使うだけです: {}", y);
}
fn main() {
    foo(3, 4);
}

 ユースケース例は以下。

トレイトを実装する際、 特定の型シグニチャが必要だけれども、自分の実装の関数本体では引数の1つが必要ない時

 値の一部を確認するなら以下。(Some内の値を使わないとき)

fn main() {
    let mut setting_value = Some(5);
    let new_setting_value = Some(10);
    match (setting_value, new_setting_value) {
        (Some(_), Some(_)) => {
            println!("既存の値の変更を上書きできません");
        }
        _ => {
            setting_value = new_setting_value;
        }
    }
    println!("設定: {:?}", setting_value);
}

 特定の位置にある値を無視するなら以下。

fn main() {
    let numbers = (2, 4, 8, 16, 32);
    match numbers {
        (first, _, third, _, fifth) => {
            println!("Some numbers: {}, {}, {}", first, third, fifth)
        },
    }
}
$ rustc main.rs
$ ./main
Some numbers: 2, 8, 32

_: 未使用ワーニング無視(変数名のプレフィクス)

 コンパイルすると未使用の変数があったら警告が出る。だが、変数名の最初に_を付与すれば警告が出ない。

fn main() {
    let _x = 5;
    let y = 10;
}
$ rustc main.rs
warning: unused variable: `y`
 --> main.rs:7:9
  |
7 |     let y = 10;
  |         ^ help: consider prefixing with an underscore: `_y`
  |
  = note: #[warn(unused_variables)] on by default

 変数_x, yはどちらも使っていない。なのに_xは警告なし。_プレフィックスで警告を消せる。

記法 違い
_ 値を束縛しない
_x 値を束縛する

 以下、Some(_s)とすることで変数sの所有権が_sにムーブしてしまう。

fn main() {
    let s = Some(String::from("Hello!"));
    if let Some(_s) = s {
        println!("found a string");
    }
    println!("{:?}", s);
}
error[E0382]: use of partially moved value: `s`
  --> main.rs:10:22
   |
7  |     if let Some(_s) = s {
   |                 -- value moved here
...
10 |     println!("{:?}", s);
   |                      ^ value used here after move
   |
   = note: move occurs because the value has type `std::string::String`, which does not implement the `Copy` trait

 これを避けるためにSome(_s)Some(_)にするといい。_はムーブしない。

$ ./main
found a string
Some("Hello!")

..: 残りを無視

struct Point {
    x: i32,
    y: i32,
    z: i32,
}
fn main() {
    let origin = Point { x: 0, y: 0, z: 0 };
    match origin {
        Point { x, .. } => println!("x is {}", x),
    }
}
$ ./main
x is 0

 ポイントはPoint { x, .. }。本来はPoint { x, y:_, z:_ }とせねばらなかった。これを..で省略した。

 タプルの場合、以下のようなパターンで省略できる。

  • (first, ..)
  • (.., last)
  • (first, .., last)
fn main() {
    let numbers = (2, 4, 8, 16, 32);
    match numbers {
//        (first, ..) => println!("{}", first), // 2
//        (.., last) => println!("{}", last), // 32
//        (.., second, ..) => println!("{}", second), // error: `..` can only be used once per tuple or tuple struct pattern
        (first, .., last) => {
            println!("{}, {}", first, last); // 2, 32
        },
    }
}
$ rustc main.rs
$ ./main
2, 32

 なお、(.., second, ..)など複数ヶ所で..を使うとエラーになる。

error: `..` can only be used once per tuple or tuple struct pattern
  --> main.rs:10:22
   |
10 |         (.., second, ..) => println!("{}", second),
   |                      ^^ can only be used once per pattern

ref, ref mut: パターンに参照を生成する

 パターンだと所有権を奪われてしまう。

fn main() {
    let robot_name = Some(String::from("Bors"));
    match robot_name {
        Some(name) => println!("Found a name: {}", name),// error[E0382]: use of partially moved value: `robot_name`
        None => (),
    }
    println!("robot_name is: {:?}", robot_name);
}
$ rustc main.rs
error[E0382]: use of partially moved value: `robot_name`
  --> main.rs:11:37
   |
8  |         Some(name) => println!("Found a name: {}", name),
   |              ---- value moved here
...
11 |     println!("robot_name is: {:?}", robot_name);
   |                                     ^^^^^^^^^^ value used here after move
   |
   = note: move occurs because the value has type `std::string::String`, which does not implement the `Copy` trait

 そこでパターンにrefを用いると参照になり所有権を奪わない。

fn main() {
    let robot_name = Some(String::from("Bors"));
    match robot_name {
        Some(ref name) => println!("Found a name: {}", name),
        None => (),
    }
    println!("robot_name is: {:?}", robot_name);
}
$ rustc main.rs
$ ./main
Found a name: Bors
robot_name is: Some("Bors")

 Some(&name)とすればいい気がするが、それはできない。「分配: 参照」の項にあるとおり。

パターンとマッチさせている値に参照が含まれる場合、値から参照を分配する必要があり、 パターンに&を指定することでそうすることができます。

 パターンの文脈では&の意味が変わる。

 パターンでは、Some(&name)でなくSome(ref name)とする。また、可変参照にしたくばSome(ref mut name)とすれば良い。参照外しは*演算子で行う。

let mut robot_name = Some(String::from("Bors"));
match robot_name {
    Some(ref mut name) => *name = String::from("Another name"),
    None => (),
}

 パターン文脈では&の意味が変わってしまうのが混乱する。

match: マッチガード match if

fn main() {
    let num = Some(4);
    match num {
        Some(x) if x < 5 => println!("less than five: {}", x),
        Some(x) => println!("{}", x),
        None => (),
    }
}
$ rustc main.rs
$ ./main
less than five: 4
  • パターン if 条件式 => 実行式

 パターンがシャドーイングしてしまう問題を回避するために、マッチガードが使える。

fn main() {
    let x = Some(5);
    let y = 10;
    match x {
        Some(50) => println!("Got 50"),
        Some(n) if n == y => println!("Matched, n = {:?}", n),
        _ => println!("Default case, x = {:?}", x),
    }
    println!("at the end: x = {:?}, y = {:?}", x, y);
}
$ rustc main.rs
$ ./main
Default case, x = Some(5)
at the end: x = Some(5), y = 10

 Some(n) if n == yがポイント。上位スコープでlet y = 10;と宣言している値を使っている。パターンでは別名nとしてyと区別させる。その上でマッチガードifを使えば、両者を比較した条件式により複雑なマッチが書ける。

 |と併用することもできる。4|5|6にマッチし、かつif y(ytrue)のときのみマッチする。

fn main() {
    let x = 4;
    let y = false;
    match x {
        4 | 5 | 6 if y => println!("yes"),
        _ => println!("no"),
    }
}
$ rustc main.rs
$ ./main
no

@束縛

enum Message {
    Hello { id: i32 },
}
fn main() {
    let msg = Message::Hello { id: 5 };
    match msg {
        Message::Hello { id: id_variable @ 3...7 } => {
            println!("Found an id in range: {}", id_variable)
        },
        Message::Hello { id: 10...12 } => {
            println!("Found an id in another range")
        },
        Message::Hello { id } => {
            println!("Found some other id: {}", id)
        },
    }
}
$ rustc main.rs
$ ./main
Found an id in range: 5
  • 列挙体::列挙子 { id: 変数名 @ 3...7 }: 列挙体にある列挙子の値である無名構造体がもつフィールドidの値が、37の間であればマッチする

 これまたややこしい。以下2つの要件があるとき@ n ... m表記が必要になる。

  • 範囲内か判定したい
  • パターンで変数名を宣言し、実行式の中で使いたい

 もし範囲の変数が不要なら{ id: 10...12 }とすればよく、@は不要。

対象環境

$ uname -a
Linux raspberrypi 4.19.42-v7+ #1219 SMP Tue May 14 21:20:58 BST 2019 armv7l GNU/Linux

前回まで