列挙型とパターンマッチング
複数の選択肢を表す「列挙型」と、それを扱う「パターンマッチング」を学びます。
列挙型(Enum)とは
列挙型は複数の選択肢のうち1つを表す型です。
enum Direction {
North,
South,
East,
West,
}
fn main() {
let dir = Direction::North;
}
列挙型の定義と使用
基本的な列挙型
enum Status {
Pending,
InProgress,
Completed,
Cancelled,
}
fn main() {
let task_status = Status::InProgress;
}
データを持つ列挙型
各バリアントがデータを持てます。
enum Message {
Quit, // データなし
Move { x: i32, y: i32 }, // 構造体風
Write(String), // 単一の値
ChangeColor(u8, u8, u8), // タプル風
}
fn main() {
let msg1 = Message::Quit;
let msg2 = Message::Move { x: 10, y: 20 };
let msg3 = Message::Write(String::from("Hello"));
let msg4 = Message::ChangeColor(255, 0, 0);
}
match式
matchは列挙型のすべてのバリアントを処理します。
#![allow(unused)]
fn main() {
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
fn value_in_cents(coin: Coin) -> u32 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
}
matchは網羅的
すべてのバリアントを扱わないとエラーになります。
#![allow(unused)]
fn main() {
fn value_in_cents(coin: Coin) -> u32 {
match coin {
Coin::Penny => 1,
// エラー!他のバリアントが抜けている
}
}
}
データの取り出し
#![allow(unused)]
fn main() {
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
}
fn process(msg: Message) {
match msg {
Message::Quit => {
println!("終了します");
}
Message::Move { x, y } => {
println!("移動: ({}, {})", x, y);
}
Message::Write(text) => {
println!("メッセージ: {}", text);
}
}
}
}
_ プレースホルダ
残りすべてにマッチします。
#![allow(unused)]
fn main() {
fn describe_number(n: i32) {
match n {
1 => println!("一"),
2 => println!("二"),
3 => println!("三"),
_ => println!("その他"), // 1, 2, 3以外すべて
}
}
}
Option型(復習と深掘り)
Optionは「値があるかないか」を表す標準の列挙型です。
#![allow(unused)]
fn main() {
enum Option<T> {
Some(T),
None,
}
}
Optionの使用
fn find_user(id: u32) -> Option<String> {
if id == 1 {
Some(String::from("太郎"))
} else {
None
}
}
fn main() {
match find_user(1) {
Some(name) => println!("見つかりました: {}", name),
None => println!("見つかりませんでした"),
}
}
Result型(復習と深掘り)
Resultは「成功か失敗か」を表す標準の列挙型です。
#![allow(unused)]
fn main() {
enum Result<T, E> {
Ok(T),
Err(E),
}
}
Resultの使用
fn divide(a: f64, b: f64) -> Result<f64, String> {
if b == 0.0 {
Err(String::from("ゼロ除算エラー"))
} else {
Ok(a / b)
}
}
fn main() {
match divide(10.0, 2.0) {
Ok(result) => println!("結果: {}", result),
Err(e) => println!("エラー: {}", e),
}
}
if let 式
特定のパターンだけ処理したい場合に便利です。
fn main() {
let some_value = Some(5);
// matchを使う場合
match some_value {
Some(n) => println!("値: {}", n),
None => (), // 何もしない
}
// if letを使う場合(簡潔)
if let Some(n) = some_value {
println!("値: {}", n);
}
}
if let else
fn main() {
let coin = Coin::Quarter;
if let Coin::Quarter = coin {
println!("25セント!");
} else {
println!("25セントではない");
}
}
while let 式
パターンがマッチする間ループします。
fn main() {
let mut stack = vec![1, 2, 3];
while let Some(top) = stack.pop() {
println!("{}", top);
}
}
// 出力: 3, 2, 1
列挙型のメソッド
列挙型にもメソッドを定義できます。
enum Status {
Pending,
InProgress,
Completed,
}
impl Status {
fn description(&self) -> &str {
match self {
Status::Pending => "未着手",
Status::InProgress => "進行中",
Status::Completed => "完了",
}
}
fn is_done(&self) -> bool {
matches!(self, Status::Completed)
}
}
fn main() {
let status = Status::InProgress;
println!("{}", status.description());
println!("完了?: {}", status.is_done());
}
実践例: 状態管理
#![allow(unused)]
fn main() {
#[derive(Debug)]
enum TaskState {
Todo,
InProgress { progress: u8 },
Done { completed_at: String },
}
struct Task {
name: String,
state: TaskState,
}
impl Task {
fn new(name: String) -> Task {
Task {
name,
state: TaskState::Todo,
}
}
fn start(&mut self) {
self.state = TaskState::InProgress { progress: 0 };
}
fn update_progress(&mut self, progress: u8) {
if let TaskState::InProgress { progress: ref mut p } = self.state {
*p = progress;
}
}
fn complete(&mut self, completed_at: String) {
self.state = TaskState::Done { completed_at };
}
}
}
まとめ
| 概念 | 説明 |
|---|---|
| 列挙型 | 複数の選択肢のうち1つを表す |
| バリアント | 列挙型の各選択肢 |
match | 網羅的なパターンマッチング |
if let | 特定パターンのみ処理 |
Option | 値の有無を表す |
Result | 成功/失敗を表す |
確認テスト
Q1. match式の特徴として正しいのは?
matchは網羅的で、すべてのケースを処理しないとコンパイルエラーになります。_を使えば残りすべてをまとめて処理できます。
Q2. if letを使うべき場面は?
if letは特定のパターンのみ処理し、残りは無視(またはelseで処理)したい場合に便利です。
Q3. 以下のコードの出力は?let light = Light::Yellow; let action = match light { Light::Red => "止まれ", Light::Yellow => "注意", Light::Green => "進め" }; println!("{}", action);
lightはLight::Yellowなので、matchで"注意"が返されます。
Q4. 以下のコードがコンパイルエラーになる理由は?enum Color { Red, Green, Blue } fn describe(c: Color) -> &str { match c { Color::Red => "赤", Color::Green => "緑" } }
matchは網羅的である必要があり、Color::Blueのケースが抜けているためコンパイルエラーになります。
Q5. Option<T>のNoneは何を表す?
Option<T>のNoneは「値が存在しない」ことを表します。Some(T)は「値が存在する」ことを表します。
次のドキュメント: 03_traits.md