Rustのパターンマッチを完全に理解した (2)

本記事はRustその2 Advent Calendar 2018 の17日目の記事です。

本blogで Rustのパターンマッチを完全に理解した を書いてから約1年が経過しました。

その間にRustにはパターンマッチ関連の機能もいくつか追加され、以前の内容ではパターンマッチを完全に理解したとは言えなくなってしまいました。

そこで、本記事では

  • Rustに最近追加されたパターンマッチ関連の仕様
  • 最近追加されたわけではないが、以前の記事で書き忘れた機能

について紹介したいと思います。

フィールド名の省略

まずは前回書き忘れた事を。
(Version 1.17.0 2017-04-27) からできた事ですが・・・

次のように構造体をフィールド名と同名の変数で初期化する場合、

1
2
3
4
5
6
7
8
struct Foo {
xxx: u8,
yyy: u8,
}

fn create_foo(xxx: u8, yyy: u8) {
let _value = Foo { xxx: xxx, yyy: yyy };
}

下のようにフィールド名を省略することができる、という事は皆さんご存知かと思います。

1
2
3
fn create_foo(xxx: u8, yyy: u8) {
let _value = Foo { xxx, yyy };
}

この記法はパターンマッチでも使用できます。

つまり、次のようにフィールド名と同じ名前の変数に束縛する場合は・・・

1
2
3
fn bind_foo(foo: Foo) {
let Foo { xxx: xxx, yyy: yyy } = foo;
}

下のように、フィールド名を省略することができます。

1
2
3
fn bind_foo(foo: Foo) {
let Foo { xxx, yyy } = foo;
}

もちろん、参照を束縛する場合もOK。

1
2
3
fn bind_foo_ref(foo: Foo) {
let &Foo { ref xxx, ref yyy } = &foo;
}

&の省略と既定の束縛モード

Version 1.26.0 (2018-05-10) で「えっ?わかりにくくない?」と思うような機能が入りました。

まず、次の例を見てください。

1
let &(ref a, ref b) = &(1, 2);

「タプルの参照」から「タプルの要素の参照」を取得しています。
参照からは所有権を奪えないため、このパターンはよく使われますね。

このような&が含まれるパターンでは & が省略できるようになりました。
つまり、次のように書けるという事です。

1
let (ref a, ref b) = &(1, 2);

さらに & が省略されたパターンの内側では変数名の前に ref を付けなくても ref を付けた時と同様に参照が束縛されるようになりました。
つまり、ref も省略できるという事です。

1
let (a, b) = &(1, 2);

& はいくつでも省略できます。

1
let (a, b) = &&&&&&&&&(1, 2);

ref を付けなくても参照が束縛されるのは & が省略されたパターンの内側(下の例では a , b )だけです。
& が省略されたパターンの外側(下の例では x, y )では値が束縛されます。

1
2
3
let ((a, b), &(x, y)) = (&(1, 2), &(3, 4));
// a, b は &i32 (参照が束縛される)
// x, y は i32 (値が束縛される)

& mut を省略することもできます。
& mut が省略されたパターンの内側では、束縛する変数に ref mut が付いている場合と同じように、ミュータブル参照が変数に束縛されます。

1
2
3
// 省略しない場合
let &mut (ref mut a, ref mut b) = &mut (1, 2);
*a = 1;
1
2
3
// 省略した場合
let (a, b) = &mut (1, 2);
*a = 1;

& mut& の両方を省略したパターンの内側では ref mut ではなく、 ref が付いている場合と同じように、共有参照が変数に束縛されます。

1
2
// 省略しない場合
let &mut (_, &(ref x, ref y)) = &mut (1, &(2, 3));
1
2
// 省略した場合
let (_, (x, y)) = &mut (1, &(2, 3));

スライスパターン

Version 1.26.0 (2018-05-10) で追加されたパターンはもう一つあります。

それはスライスパターンです。
例を見てみましょう。

1
2
3
4
5
pub fn f(s: &[u32]) {    
if let &[x, y, z] = s {
// ここに処理を書く
}
}

このように、スライスに一致するパターンを書く事ができます。

また、固定長配列にも使用できます。
固定長配列の場合は配列長を考慮して必ずマッチするかどうかが判定されます。

1
2
3
fn f(s: [u32; 3]) {
let [x, y, z] = s; // このパターンは必ずマッチするので if let ではなく let を使用
}

一方、残念ながら Vec をそのままマッチさせることはできません。

1
2
3
4
let v = vec![1, 3, 3];
if let &[x, y, z] = &v {
// ここに処理を書く
}
1
2
3
4
5
6
7
error[E0529]: expected an array or slice, found `std::vec::Vec<{integer}>`
--> examples\slice_pattern.rs:3:13
|
2 | if let &[x, y, z] = &v {
| ^^^^^^^^^ pattern cannot match with input type `std::vec::Vec<{integer}>`

error: aborting due to previous error

スライスか固定長配列に変換してからマッチさせる必要があります。

1
2
3
4
let v = vec![1, 3, 3];
if let &[x, y, z] = &v[..] {
// ここに処理を書く
}

スライスの複数の要素にマッチする .. というパターンも提案されていますが、これは安定化されていません。
今後に期待しましょう。

1
2
3
4
5
6
#![feature(slice_patterns)]
fn f(s: &[u32]) {
if let &[head, ..] = s {
//
}
}

パターンの括弧

次の例を見てください。

1
2
let a = 20;
let &x = &a;

変数 x の前に & を付け、参照外しをしています。

次に、x に他の値を代入したくなったとします。

1
2
3
let a = 20;
let &x = &a;
x = 4;
1
2
3
4
5
6
7
8
9
10
error[E0384]: cannot assign twice to immutable variable `x`
--> examples\parentheses.rs:3:5
|
2 | let &x = &a;
| -
| |
| first assignment to `x`
| help: make this binding mutable: `mut x`
3 | x = 4;
| ^^^^^ cannot assign twice to immutable variable

当然のことながら代入できませんね。 x はミュータブルではないので。
そこで x の前に mut を付けて代入できるようにしましょう。

1
2
3
let a = 20;
let &mut x = &a;
x = 4;
1
2
3
4
5
6
7
8
 --> examples\parentheses.rs:2:9
|
2 | let &mut x = &a;
| ^^^^^^ types differ in mutability
|
= note: expected type `&{integer}`
found type `&mut _`
= help: did you mean `mut x: &&{integer}`?

おや?ミュータブル参照の参照外し &mut x になってしまいました。

そうです。mut が二つの意味で使われているため、表現できないパターンが存在するのです。

というのは先日までの話。
Version 1.31.0 (2018-12-06) からはパターンで括弧が使えるようになり、この問題はなくなりました。

1
2
3
let a = 20;
let &(mut x) = &a;
x = 4;

これで共有参照の参照外しをしつつ、変数をミュータブルにすることができるようになりました。

.. を利用したprivateフィールドを含む構造体へのマッチ

構造体でパターンマッチを使用できるのはアクセス可能なフィールドだけです。

1
2
3
4
5
6
7
8
9
10
mod my_module {
pub struct S {
pub a: u32,
b: u32,
}
}
use crate::my_module::S;
fn f(s: S) {
let S { a, b } = s;
}
1
2
3
4
5
error[E0451]: field `b` of struct `my_module::S` is private
--> examples\private_field.rs:9:16
|
9 | let S { a, b } = s;
| ^ field `b` is private

このように S::b はprivateフィールドの為、モジュール外からは使用できません。

が・・・例外が1つだけあります。

指定されていない全てのフィールドを無視するパターン .. を使用すれば、privateフィールドを含む構造体にモジュール外からマッチさせることができます。

1
2
3
fn f(s: S) {
let S { a, .. } = s;
}

参考

Rustのリリースノート
Slice patterns - The Edition Guide
rust-lang/rfcs/text/2005-match-ergonomics.md