Rustのフロー制御:if-else、match、if letの完全ガイド
Rustの
matchはswitchとは異なります。コンパイラがすべてのケースを網羅しているかどうかを確認するため、より強力なのです。
制御フローはプログラムの根幹をなすものです。従来の if/else に加え、Rust には match パターンマッチング と if let シンタックスシュガー という 2 つの大きな強みがあります。
1. 学習内容
- 条件分岐には
if/else if/elseを使用してください - パターンマッチングには
matchを使用してください - 「match」およびワイルドカードの徹底的なチェック
_ if letを使用して、シングルモードのマッチングを簡略化する- 「match guard」ルールに条件を追加する
2. 「じゃんけん」の審判の物語
(1) 試行錯誤:if-else文を使って「じゃんけん」の判定ロジックを書く
エマはゲーム開発者で、自分のゲームにじゃんけんの機能を追加したいと考えています:
Player's turn: Rock (1), Scissors (2), Paper (3)
Computer: Pick one at random
Needs to be determined: Win, Lose, or Tie
判定に if/else を使用するコード:
if Player == 1 && Computer == 1 → Tie
else if Player == 1 && Computer == 2 → Win
else if Player == 1 && Computer == 3 → Lose
... Still needs 6 branches!
if/elseを大量に書き連ねると、特定の組み合わせを見落としやすくなり、読みづらくなってしまいます。
(2) Rustのmatch文に対する洗練された解決策
fn main() {
let player = 1; // Rock
let computer = 2; // Scissors
let result = match (player, computer) {
(1, 1) | (2, 2) | (3, 3) => "Tie",
(1, 2) | (2, 3) | (3, 1) => "You Win!",
(1, 3) | (2, 1) | (3, 2) => "You Lose!",
_ => "Invalid Punch",
};
println!("Results: {}", result);
}
matchここはまるで審判のようなものです。プレイヤーとコンピュータのパンチの組み合わせを、漏れもなく曖昧さもなく、1対1で対応させています。コンパイラは、9つの組み合わせをすべて記述しているかどうかをチェックします。
3. if/else による条件分岐
(1) 基本的な使い方
fn main() {
let number = 7;
if number < 5 {
println!("A number less than 5");
} else if number == 5 {
println!("The number equals 5");
} else {
println!("The number is greater than 5");
}
}
(2) if が式である場合、値を返すことができる
let condition = true;
let value = if condition { 5 } else { 6 };
// Note:The two branches must be of the same type.
println!("value = {}", value); // value = 5
重要:
ifの 2 つの分岐は、同じ型でなければなりません。if true { 5 } else { "six" }を使用すると、コンパイルエラーが発生します。
4. パターンマッチング
(1) 基本的な構文
graph TB
A[match Expressions] --> B[Each branch = A Pattern]
B --> C[Pattern match successful → Run this branch]
B --> D[Pattern Mismatch → Try the next branch]
A --> E[Finally, there must be _ Wildcard Fallback]
let number = 3;
match number {
1 => println!("one"),
2 => println!("two"),
3 => println!("three"),
_ => println!("other"), // Wildcard——Match all remaining cases
}
(2) 「match」の主なルール
| ルール | 説明 |
|---|---|
| 網羅性 | 考えられるすべての値を網羅しなければならない。いずれかの分岐を省略すると、コンパイルエラーが発生する |
ワイルドカード _ |
残りのすべてのケースに一致する。末尾に配置される |
マルチモード | |
1つの分岐が複数のパターンに一致する:1 | 2 => |
| 範囲の一致 | 1..=5 1 から 5 までの閉区間と一致する |
| ガード | 追加条件:x if x > 5 => |
(3) if と match の比較
| 特徴 | if/else |
match |
|---|---|---|
| 照合方法 | ブール条件 | パターン照合 |
| 網羅的なチェック | なし(この場合は省略可) | 必須(すべての値を網羅する必要がある) |
| 分解機能 | なし | 列挙型、タプル、構造体の分解に対応 |
| 複数値の一致 | ` | |
| ユースケース | 範囲チェック、ブール論理 | 列挙処理、値の分類、分解 |
5. if let の構文上の簡略化
特定のパターンのみに興味があり、他の値には関心がない場合、if let は match よりも簡潔です:
let optional = Some(5);
// match Writing Style (verbose)
match optional {
Some(value) => println!("The value is: {}", value),
_ => (), // You must write this empty branch.
}
// if let Writing Style (Concise)
if let Some(value) = optional {
println!("The value is: {}", value);
// No need to write _ => ()
}
(4) 制御フローキーワードのクイックリファレンス
| キーワード/構文 | 目的 | 例 |
|---|---|---|
if / else if / else |
条件分岐 | if x > 0 { ... } else { ... } |
match |
パターンマッチング | match x { 1 => ..., _ => ... } |
if let |
シングルモードのマッチングを表す構文糖衣 | if let Some(v) = x { ... } |
while |
条件分岐 | while x > 0 { ... } |
while let |
パターンマッチングループ | while let Some(v) = iter.next() { ... } |
loop |
Infinite Loop | loop { ... break; } |
| イテレータの走査 | ||
break |
ループ終了 | break; または break value; |
continue |
この反復をスキップ | continue; |
6. 完全な例
(1) ▶ サンプル:if/else による成績分類(難易度 ⭐)
// ============================================
// Use if expression to grade scores and return a string
// ============================================
fn main() {
let score = 88;
let grade = if score >= 90 {
"Excellent (A)"
} else if score >= 80 {
"Good (B)"
} else if score >= 70 {
"Intermediate (C)"
} else if score >= 60 {
"Passing Grade (D)"
} else {
"Fail (F)"
};
println!("Fractions: {}, Level: {}", score, grade);
}
出力:
Fractions: 88, Level: Good (B)
if式の各分岐は、同じ型を返さなければなりません。ここでは、すべての分岐が&strを返すため、コンパイラは問題ないと判断します。
(2) ▶ サンプル:match を使用した HTTP ステータスコードの処理 (難易度: ⭐⭐)
// ============================================
// Use match to gracefully handle HTTP Status Codes
// ============================================
fn handle_status_code(code: u16) -> &'static str {
match code {
200 => "OK: Request Successful",
201 => "Created: The resource has been created",
204 => "No Content: No results found",
301 | 302 => "Redirect: The resource has been moved",
400 => "Bad Request: Invalid request format",
401 => "Unauthorized: Unauthorized",
403 => "Forbidden: Access Denied",
404 => "Not Found: Resource does not exist",
500 => "Internal Server Error: Internal Server Error",
502 | 503 => "Server Error: The server is temporarily unavailable",
_ => "Unknown: Unknown status code",
}
}
fn main() {
let codes = [200, 404, 418, 500];
for &code in codes.iter() {
println!("Status Code {}: {}", code, handle_status_code(code));
}
}
出力:
Status Code 200: OK: Request Successful
Status Code 404: Not Found: Resource does not exist
Status Code 418: Unknown: Unknown status code
Status Code 500: Internal Server Error: Internal Server Error
|この演算子を使用すると、1つの分岐で複数のパターンに一致させることができ、コードの重複を回避できます。_ワイルドカードを使用することで、すべてのステータスコードを確実に網羅できます。
(3) ▶ サンプル:マッチガードと if let (難易度 ⭐⭐)
// ============================================
// match Guard + if let Simplified Pattern Matching
// ============================================
fn main() {
let number = Some(42);
// match Guard: Add additional conditions to the pattern
match number {
Some(x) if x > 100 => println!("Very Large Numbers: {}", x),
Some(x) if x > 50 => println!("Big Numbers: {}", x),
Some(x) => println!("Small numbers: {}", x),
None => println!("No numbers"),
}
// if let Syntactic sugar -- Only care about the Some case
let value = Some("Rust");
if let Some(lang) = value {
println!("Language is: {}", lang);
}
// if let with else
let empty: Option<i32> = None;
if let Some(x) = empty {
println!("Not null: {}", x);
} else {
println!("No value");
}
}
出力:
Small numbers: 42
Language is: Rust
No value
match文で
ifキーワードを使用すると、パターンの後に追加の条件を指定できます。この分岐は、パターンが一致し、かつ条件が真の場合にのみ実行されます。if letは、「1つのパターンのみを対象とする」ような場合に適しています。
(4) ▶ サンプル:match を使った列挙型とタプルの分解 (難易度: ⭐⭐⭐)
// ============================================
// match Deconstruction: Enumeration + Tuple + Guard
// ============================================
enum Shape {
Circle(f64),
Rectangle(f64, f64),
Triangle(f64, f64, f64),
}
fn describe_shape(shape: &Shape) -> String {
match shape {
Shape::Circle(radius) if *radius > 10.0 => {
format!("Big Circle (Radius {:.1})", radius)
}
Shape::Circle(radius) => {
format!("Small Circle (Radius {:.1})", radius)
}
Shape::Rectangle(w, h) if w == h => {
format!("Square (Side length {:.1})", w)
}
Shape::Rectangle(w, h) => {
format!("Rectangle (Width {:.1}, Height {:.1})", w, h)
}
Shape::Triangle(a, b, c) => {
format!("Triangle (Sides {:.1}, {:.1}, {:.1})", a, b, c)
}
}
}
fn main() {
let shapes = [
Shape::Circle(5.0),
Shape::Circle(15.0),
Shape::Rectangle(4.0, 4.0),
Shape::Rectangle(3.0, 5.0),
Shape::Triangle(3.0, 4.0, 5.0),
];
println!("=== Graphical Description ===");
for shape in &shapes {
println!("{}", describe_shape(shape));
}
println!("\n=== Coordinate Classification ===");
let points: [(i32, i32); 4] = [(0, 0), (3, 0), (0, -2), (5, 7)];
for point in points {
let desc = match point {
(0, 0) => "Origin".to_string(),
(x, 0) => format!("On x-axis (x={})", x),
(0, y) => format!("On y-axis (y={})", y),
(x, y) if x > 0 && y > 0 => format!("First Quadrant ({}, {})", x, y),
(x, y) => format!("Other Quadrants ({}, {})", x, y),
};
println!("Point {:?}: {}", point, desc);
}
}
出力:
=== Graphical Description ===
Small Circle (Radius 5.0)
Big Circle (Radius 15.0)
Square (Side length 4.0)
Rectangle (Width 3.0, Height 5.0)
Triangle (Sides 3.0, 4.0, 5.0)
=== Coordinate Classification ===
Point (0, 0): Origin
Point (3, 0): On x-axis (x=3)
Point (0, -2): On y-axis (y=-2)
Point (5, 7): First Quadrant (5, 7)
matchのデストラクチャリング機能を使えば、列挙型のバリアントを同時にマッチングさせ、内部値を抽出するとともに、ガード条件を適用して、正確な分類ロジックを実装することができます。コンパイラによる網羅的なチェックにより、いかなるケースも見落とすことがありません。
(5) ▶ サンプル:if let と while let の実践的な応用(難易度:⭐⭐)
// ============================================
// if let and while let: Simplify Option/Result Processing
// ============================================
fn divide(a: f64, b: f64) -> Option<f64> {
if b == 0.0 {
None
} else {
Some(a / b)
}
}
fn main() {
println!("=== if let Processing Option ===");
let results = [divide(10.0, 2.0), divide(8.0, 0.0), divide(15.0, 3.0)];
for result in &results {
if let Some(value) = result {
println!("Division Successful: {:.2}", value);
} else {
println!("Division Failed: Divisor is zero");
}
}
println!("\n=== if let Chain Processing ===");
let alice_input: Option<&str> = Some("42");
let bob_input: Option<&str> = None;
if let Some(num_str) = alice_input {
if let Ok(num) = num_str.parse::<i32>() {
println!("Alice's number: {}", num);
}
}
if let Some(num_str) = bob_input {
if let Ok(num) = num_str.parse::<i32>() {
println!("Bob's number: {}", num);
}
} else {
println!("Bob has no data input");
}
println!("\n=== while let Simulation Iteration ===");
let mut stack = vec![3, 2, 1];
while let Some(top) = stack.pop() {
println!("Popped: {}", top);
}
println!("The stack is empty");
}
出力:
=== if let Processing Option ===
Division Successful: 5.00
Division Failed: Divisor is zero
Division Successful: 5.00
=== if let Chain Processing ===
Alice's number: 42
Bob has no data input
=== while let Simulation Iteration ===
Popped: 1
Popped: 2
Popped: 3
The stack is empty
if letは、Some/Okのみを扱い、その他のケースを無視できるシナリオに適しています。while letは、Noneに遭遇するまで値を反復処理するシナリオ(pop()のスタック操作など)に適しています。どちらも、完全なmatchよりも簡潔です。
❓ よくある質問
match と switch の違いは何ですか?match は switch よりもはるかに強力です。match チェックがそれほど重要なのでしょうか?if let はどのような場合に使用すべきですか?match におけるワイルドカード「_」と変数名の違いは何ですか?if 式の 2 つの分岐の型が異なる場合はどうすればよいですか?📖 まとめ
if/elseは(値を返すことのできる)式であり、すべての分岐は同じ型でなければならないmatchパターンマッチングを行う際、コンパイラは網羅的なチェックを強制します- ワイルドカード
_は、残りのすべての値に一致し、一致部分の末尾に配置されます if letはmatchの構文糖であり、「1つのパターンのみを扱う」ような場面に適しています。- guard(
ifという条件付き)により、パターンマッチングの柔軟性が高まります |演算子を使用すると、分岐で複数のパターンに一致させることができます
📝 練習問題
- 難易度 ⭐:
matchを使用して、数字 0~5 を対応する英語の単語(zero/one/two/three/four/five)にマッピングし、それ以外の数字に対してはすべて「unknown」を返す関数fn number_to_word(n: i32) -> &'static strを記述してください。 - 難易度 ⭐⭐: 列挙型
enum TrafficLight { Red, Yellow, Green }を定義し、matchを使用して各色に対応する「待機時間」を返す(赤:30秒、黄:3秒、緑:45秒)。 - 難易度 ⭐⭐⭐ :関数
fn describe_point(point: (i32, i32))を記述し、matchを使用して 2 次元座標上の点を処理するようにします。その点が原点(0,0)にある場合は「origin」を出力し、 その点が x 軸上にある場合は(x,0)、「on x-axis」を出力し、y 軸上にある場合は(0,y)、「on y-axis」を出力する。それ以外の場合は、座標値を出力する。



