データバリアントのマッチング
CoddyのRustジャーニー「Object Oriented Programming」セクションの一部 — レッスン 16/61。
前のレッスンでは、異なるバリアントを処理するためにenumのメソッド内でmatchを使用しました。次は、matchが実際にそれらのバリアント内に格納されたデータをどのように抽出するのか、構造分解(destructuring)と呼ばれる手法に焦点を当てましょう。
データを保持するenumのバリアントに対してマッチングを行うとき、そのデータをパターン内で直接変数にバインドすることができます:
enum Event {
Click { x: i32, y: i32 },
KeyPress(char),
Resize(u32, u32),
}
fn handle_event(event: Event) {
match event {
Event::Click { x, y } => {
println!("Clicked at ({}, {})", x, y);
}
Event::KeyPress(key) => {
println!("Key pressed: {}", key);
}
Event::Resize(width, height) => {
println!("Resized to {}x{}", width, height);
}
}
}
各アームは、バリアントの構造に基づいて異なる方法でデータを抽出します。構造体形式のバリアントの場合、{ field_name } を使用して名前付きフィールドを取り出します。タプル形式のバリアントの場合、(width, height) のような位置変数を使用します。
選択した変数名は、そのmatchアームの中で利用可能になります。どのバリアントであるかを確認することと、そのペイロードにアクセスすることを同時に行えるため、これは強力です。
コンパイラがすべてのバリアントを確実に処理するようにし、抽出された値はすぐに使用できます。追加の手順は必要ありません。
チャレンジ
簡単さまざまな種類のユーザーコマンドを処理するコマンドプロセッサを構築しましょう。各コマンドは異なるデータを保持しており、プロセッサはパターンマッチングを使用してそのデータを抽出し、適切なレスポンスを生成します。
コードを整理するために、2つのファイルを作成します:
command.rs: 3つのバリアントを持つパブリックなCommand列挙型を定義します:Say— 1つのStringメッセージを保持します(タプル構文)Move— 名前付きフィールドdirection(String) とsteps(u32) を保持しますCalculate— 加算する2つのi32値を保持します(タプル構文)
matchを使用して各バリアントをデストラクト(構造分解)し、実行されたアクションを説明するStringを返すexecuteメソッドを実装します。main.rs: 作成したコマンドモジュールを取り込み、提供された入力を使用して各コマンドバリアントのインスタンスを1つずつ作成し、それぞれに対してexecuteメソッドを呼び出して結果を出力します。
execute メソッドは、各バリアントをデストラクトしてそのデータにアクセスし、以下の正確な形式でメッセージを返す必要があります:
Sayの場合:Saying: {message}Moveの場合:Moving {steps} steps {direction}Calculateの場合:{a} + {b} = {sum}
出力は、各コマンドの結果をそれぞれの行に表示する必要があります:
Saying: {message}
Moving {steps} steps {direction}
{a} + {b} = {sum}例えば、sayメッセージが "Hello world"、移動が 5 ステップで "north"、計算が 10 と 25 の場合、出力は以下のようになります:
Saying: Hello world
Moving 5 steps north
10 + 25 = 35入力として、sayメッセージ、方向、ステップ数、および計算する2つの数値の計5つを受け取ります。
チートシート
match によるパターンマッチングを使用すると、enumのバリアントをデストラクト(構造分解)し、そのデータをパターン内で直接抽出できます。
タプルバリアントの場合は、位置に基づいた変数を使用します:
enum Event {
KeyPress(char),
Resize(u32, u32),
}
match event {
Event::KeyPress(key) => {
println!("Key: {}", key);
}
Event::Resize(width, height) => {
println!("{}x{}", width, height);
}
}構造体形式のバリアントの場合は、名前付きフィールドの構文を使用します:
enum Event {
Click { x: i32, y: i32 },
}
match event {
Event::Click { x, y } => {
println!("Clicked at ({}, {})", x, y);
}
}抽出された変数は、それぞれのマッチアーム内で利用可能になり、バリアントの型のチェックとデータへのアクセスを同時に行うことができます。
自分で試してみよう
mod command;
use command::Command;
fn main() {
// 入力を読み込む
let mut say_message = String::new();
std::io::stdin().read_line(&mut say_message).expect("Failed to read line");
let say_message = say_message.trim().to_string();
let mut direction = String::new();
std::io::stdin().read_line(&mut direction).expect("Failed to read line");
let direction = direction.trim().to_string();
let mut steps_input = String::new();
std::io::stdin().read_line(&mut steps_input).expect("Failed to read line");
let steps: u32 = steps_input.trim().parse().expect("Failed to parse steps");
let mut num1_input = String::new();
std::io::stdin().read_line(&mut num1_input).expect("Failed to read line");
let num1: i32 = num1_input.trim().parse().expect("Failed to parse num1");
let mut num2_input = String::new();
std::io::stdin().read_line(&mut num2_input).expect("Failed to read line");
let num2: i32 = num2_input.trim().parse().expect("Failed to parse num2");
// TODO: say_message を使用して Say コマンドを作成し、実行する
// TODO: direction と steps を使用して Move コマンドを作成し、実行する
// TODO: num1 と num2 を使用して Calculate コマンドを作成し、実行する
// TODO: 各コマンドの実行結果を出力する
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。