データを持つ Enum
CoddyのRustジャーニー「Object Oriented Programming」セクションの一部 — レッスン 14/61。
Rustの基本的な列挙型(enum)を使用すると、Direction::NorthやStatus::Activeのように、固定されたバリアントのセットを持つ型を定義できます。しかし、Rustの列挙型はそれよりもはるかに強力です。各バリアントは独自のデータを保持することができ、列挙型は現実世界のシナリオをモデリングする上で非常に柔軟なものになります。
異なるメッセージタイプが異なる情報を運ぶメッセージングシステムを考えてみましょう。テキストメッセージには内容があり、移動コマンドには座標があり、終了信号には何も含まれません。Rustでは、これらすべてを単一のenumで表現できます:
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(u8, u8, u8),
}
各バリアントは異なる形状を持っています。Quitはデータを保持しません。単なるシグナルです。
Move は、名前付きフィールドを持つ構造体のような構文を使用します。Write は、タプル構文を使用して単一の String を保持します。ChangeColor は、RGBコンポーネントを表す3つの値を保持します。
これらのバリアントのインスタンスを作成すると、次のようになります。
let m1 = Message::Quit;
let m2 = Message::Move { x: 10, y: 20 };
let m3 = Message::Write(String::from("Hello"));
let m4 = Message::ChangeColor(255, 128, 0);
このアプローチの素晴らしい点は、m1、m2、m3、およびm4がすべて同じ型、つまりMessageであることです。これらを同じコレクションに格納したり、同じ関数に渡したり、単一の関数から任意のバリアントを返したりすることができます。enumは、各バリアントが必要とする特定の情報を保持しながら、異なるデータの形状を1つの型の下に統合します。
チャレンジ
簡単異なる種類のアラートを表現するために、データを持つ列挙型(enum)を使用した通知システムを構築しましょう。各通知タイプは異なる情報を保持します。メッセージを持つもの、数値を持つもの、そして単純なシグナルであるものがあります。
コードを整理するために2つのファイルを作成します:
notification.rs: 以下の3つのバリアントを持つパブリックなNotification列挙型を定義します:Alert— 1つのStringメッセージを保持(タプル構文)Reminder— 名前付きフィールドtitle(String) とminutes(u32) を保持Dismiss— データを保持しない、単なるシグナル
main.rs: 通知モジュールを取り込み、各バリアントのインスタンスを1つずつ作成します。次に、各通知に関する情報を出力し、3つのバリアントがすべて同じ型でありながら異なるデータを保持していることを示します。
出力する際は、デバッグフォーマット({:?})を使用して各通知を表示してください。この方法で出力できるように、列挙型に Debug トレイトを継承(derive)させる必要があります。
出力は、3つの通知すべてをそれぞれ個別の行に表示する必要があります:
Alert("{message}")
Reminder { title: "{title}", minutes: {minutes} }
Dismiss例えば、アラートメッセージが "Server down"、リマインダーのタイトルが "Meeting" で 30 分の場合、出力は以下のようになります:
Alert("Server down")
Reminder { title: "Meeting", minutes: 30 }
Dismissアラートメッセージ、リマインダーのタイトル、リマインダーの分の3つの入力を受け取ります。
チートシート
Rustの列挙型(Enum)のバリアントは、異なる型のデータを保持することができ、単一の型の下で多様なシナリオをモデル化するための強力な手段となります。
データを持つ列挙型の定義
各バリアントは独自のデータ構造を持つことができます:
enum Message {
Quit, // データなし
Move { x: i32, y: i32 }, // 名前付きフィールド(構造体形式)
Write(String), // 単一の値(タプル形式)
ChangeColor(u8, u8, u8), // 複数の値(タプル形式)
}
列挙型インスタンスの作成
let m1 = Message::Quit;
let m2 = Message::Move { x: 10, y: 20 };
let m3 = Message::Write(String::from("Hello"));
let m4 = Message::ChangeColor(255, 128, 0);
保持するデータが異なっていても、すべてのインスタンスは同じ型(Message)です。これにより、コレクションに格納したり、関数に渡したり、関数から返したりといった操作を統一的に行うことができます。
Debugトレイト
{:?} フォーマットを使用して列挙型を出力するには、Debug トレイトを継承(derive)します:
#[derive(Debug)]
enum Message {
// バリアント...
}
自分で試してみよう
mod notification;
use notification::Notification;
fn main() {
// 入力を読み込む
let mut alert_message = String::new();
std::io::stdin().read_line(&mut alert_message).expect("Failed to read line");
let alert_message = alert_message.trim().to_string();
let mut reminder_title = String::new();
std::io::stdin().read_line(&mut reminder_title).expect("Failed to read line");
let reminder_title = reminder_title.trim().to_string();
let mut minutes_input = String::new();
std::io::stdin().read_line(&mut minutes_input).expect("Failed to read line");
let reminder_minutes: u32 = minutes_input.trim().parse().expect("Failed to parse minutes");
// TODO: alert_message を使用して Alert 通知を作成する
// TODO: reminder_title と reminder_minutes を使用して Reminder 通知を作成する
// TODO: Dismiss 通知を作成する
// TODO: デバッグフォーマット {:?} を使用して各通知を表示する
// 各通知はそれぞれの行に表示される必要がある
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。