はじめに
struct は、常に一緒に扱うフィールドをまとめます。一方、enum は、複数の選択肢のいずれかになる値を表し、それぞれの選択肢に異なるデータを持たせることができます。Rust の match 式を使うと、これらの選択肢を明示的に処理できます。
この実験では、メッセージを表す enum を完成させ、1 つのバリアントから座標を分解して取り出し、新しいバリアントを追加してモデルを拡張します。メッセージ処理を実行する前に、可能なすべてのバリアントを網羅する必要がある理由を、コンパイラーのエラーから確認します。
データを保持するバリアントを定義する
このステップでは、1 つの Message 型に対して、3 種類の選択肢を定義します。
用意されているプロジェクトに移動し、ソースファイルを開きます。
cd /home/labex/project/message-processor
nano src/main.rs
enum の宣言では、中括弧の中に バリアント を列挙します。コードからバリアントを作成するときは、enum の名前、::、バリアント名の順に記述します。同じ enum のバリアントごとに、異なる種類のデータを持たせることができます。
Step 1 のコメントを次の内容に置き換えます。
Text(String),
Move { x: i32, y: i32 },
Stop,
Text(String) は名前のない値を 1 つ保持します。Rust では、これを タプル型バリアント と呼びます。Move { x, y } は名前付きフィールドを保持するため、構造体型バリアント と呼びます。Stop は追加のデータを持たないため、ユニットバリアント と呼びます。まずは正式名称を暗記するよりも、「名前のない値が 1 つ、名前付きフィールド、データなし」という形を理解することが重要です。形は異なりますが、作成された値はすべて同じ Message 型になります。
Ctrl+O で保存し、Enter を押してから、Ctrl+X で終了します。用意されているプロセッサーをチェックして実行します。
cargo check
cargo run --quiet
出力は次のようになります。
Text: Maintenance at 9
Move received
Stop requested
用意されている match は、各バリアントに対応するアームを 1 つずつ選択します。各アームは パターン => 動作 の形式で記述し、短いアームはカンマで区切ります。Text(text) パターンは、保持されている文字列に名前を付けます。Move アームでは x と y にも名前を付け、一時的に (x, y) としてまとめています。ここでの括弧は、用意されている足場コードでのみ使用する 2 値のタプルを作成します。この Lab の学習目標はタプルではありません。_coordinates のように名前の先頭に _ を付けると、この一時的な値を使わないことが意図的であると Rust に伝えられます。これにより、最初の実行で警告が表示されません。次のステップでは、この足場コードを置き換え、両方の座標を出力に表示します。
バリアントのデータを分解して使う
このステップでは、Move の match アームを変更し、パターンで取り出した座標を出力に表示します。
パターンを使うと、マッチしたバリアントの中身を分解できます。構造体型バリアントでフィールド名を記述すると、アームの式から使用できる束縛が作成されます。
ソースファイルを開きます。
nano src/main.rs
Move アーム全体を次の内容に置き換えます。
Message::Move { x, y } => println!("Move to ({x}, {y})"),
Move メッセージを受け取ると、パターンによって保持されているフィールドがローカル名 x と y に束縛されます。出力式ではこれらの名前をそのまま使用できるため、別途フィールドにアクセスしたり、キャストしたりする必要はありません。
保存して終了し、次のコマンドを実行します。
cargo run --quiet
中央の行が次のようになっていれば成功です。
Move to (3, -2)
y の値が負数になっていることから、固定した座標を出力したのではなく、実際のバリアントが保持しているデータをアームで使用したことが分かります。
網羅的な match を拡張する
このステップでは、Pause メッセージを追加し、コンパイラーによる網羅性チェックを確認してから、新しい選択肢を処理します。
match は、enum が取り得るすべての値を処理しなければなりません。この規則により、バリアントを追加すると、その新しいケースへの対応が必要なすべての match を特定できます。
ファイルを開き、Stop のすぐ下に次のバリアントを追加します。
nano src/main.rs
Pause(u32),
保存して終了し、プログラムをチェックします。ここでは失敗するのが正しい動作です。
cargo check
診断メッセージの次の部分を確認します。
error[E0004]: non-exhaustive patterns: `Message::Pause(_)` not covered
enum に Pause の値を格納できるようになった一方で、その値をどう処理するかを示すアームがないため、コンパイラーは match message を指摘しています。
もう一度ソースファイルを開きます。Stop アームの下に次のアームを追加します。
Message::Pause(seconds) => println!("Pause for {seconds} seconds"),
続いて、main にある用意された process(Message::Pause(5)); 行の先頭から // を削除します。パターンによって、保持されている u32 の値にローカル名 seconds が付けられます。
保存して終了し、チェックと実行を行います。
cargo check
cargo run --quiet
完全な出力は次のようになります。
Text: Maintenance at 9
Move to (3, -2)
Stop requested
Pause for 5 seconds
チェックが成功したことで、match が再び網羅的になったことを確認できます。また、4 行目の出力から、新しいアームが Pause の時間を分解して取り出したことが分かります。
まとめ
ユニット型、タプル型、構造体型の enum バリアントを定義し、match パターンで保持データを分解して取り出しました。また、新しい選択肢を追加した後、コンパイラーエラー E0004 を利用して match を網羅的に拡張しました。


