こんにちは!Dartの奥深い世界へようこそ。
FlutterやDartでの開発に慣れてくると、「もっとスマートに条件分岐を書きたい!」と思いますよね。
Dart 3で導入されたパターンマッチングは、まさにその願いを叶える強力な武器です。
今回は、その中でも特に混乱しがちな「パターンが一致しなかったとき(=失敗)に、制御フローがどう動くのか」に焦点を当てて解説します。
`if-case` と `switch式`、この2つの違いを完璧に理解して、意図しないバグを防ぎ、エレガントなコードを書けるようになりましょう!ここをクリアすれば、Dartの制御構文はもうバッチリマスターできますよ。
—
1. パターンマッチングにおける「失敗(Match Failure)」とは?
そもそも、パターンマッチングにおける「失敗」とは何でしょうか?
従来の `if (x == 5)` のような条件分岐は、「等しいか・等しくないか」の真偽値(boolean)を評価していました。
しかし、Dart 3のパターンマッチングは違います。「データ構造が、期待した型や形にフィットするかどうか」を検証します。
// リストのパターン:「整数が2つ並んだ構造」を期待している
var [a, b] = [1, 2]; // 成功:a = 1, b = 2 になる
もし、この期待した構造にデータが合致しなかった場合、これを「パターンの不一致(マッチの失敗)」と呼びます。
Dartはこの「失敗」に直面したとき、構文によって全く異なる振る舞いをします。ここが非常に重要なポイントです。
—
2. `if-case`:失敗しても「何事もなかったかのように」スルーする
まずは `if-case` から見ていきましょう。
`if-case` は、従来の `if` 文の条件部分を強力なパターンマッチに拡張したものです。
基本的な使い方とコードの意味
void processResponse(Object response) {
// 「整数のステータスコードと文字列のメッセージを持つレコード」にマッチさせたい
if-case response case (int code, String message) {
print(‘成功: ステータス $code – $message’);
}
// マッチしなくても、ここでプログラムがクラッシュすることはありません
print(‘次の処理へ進みます’);
}
制御フローのイメージ(図解的表現)
[ response ]
↓
(int, String) の構造か?
├─ はい → { ブロック内の処理を実行 } → 次の処理へ
└─ いいえ → 【何もしない(スルー)】 → 次の処理へ
`if-case` は、パターンが一致しなかった場合、単にそのブロックをスキップして何事もなかったかのように次の行へ進みます。
「もしこの形なら処理するけど、違ったら無視していいよ」という、少しお気楽な(しかし非常に実用的な)挙動をします。
—
3. `switch式`:失敗すると「容赦なくエラー」になる
次に、Dart 3で大幅にパワーアップした `switch式`(Switch Expressions)を見てみましょう。
ここで初心者の皆さんが最もハマりやすい罠があります。
switch式は「全網羅(エグハウスティブ)」が絶対のルール
`switch文` ではなく、値を返す `switch式`(`var result = switch(…) { … }`)の場合、すべての可能なパターンを網羅しなければならないという厳格なルールがあります。
もし、想定外のデータが飛び込んできて、どのケースにもマッチしなかった(=失敗した)場合、Dartは黙ってスルーしてはくれません。実行時エラー(`StateError`)をスローしてクラッシュします。
String getStatusDescription(int statusCode) {
return switch (statusCode) {
200 => ‘OK’,
400 => ‘Bad Request’,
500 => ‘Internal Server Error’,
// 【注意】もしこれ以外の数値(例: 404)が来たらどうなる?
};
}
上記のコード、実はコンパイルエラー(または実行時エラー)になります。なぜなら、`404` や `301` といった「その他の整数」に対する網羅性が抜けているからです。
安全なフォールスルーを防ぐ「ワイルドカード(`_`)」
意図しないスキップやエラーを防ぐために、必ずデフォルトケース(網羅漏れを防ぐ安全網)を用意する必要があります。Dart 3では、これをワイルドカード `_` で美しく表現できます。
String getStatusDescriptionSafe(int statusCode) {
return switch (statusCode) {
200 => ‘OK’,
400 => ‘Bad Request’,
500 => ‘Internal Server Error’,
_ => ‘Unknown Status’, // ← すべての「その他」をここで拾う(網羅性の担保)
};
}
制御フローのイメージ(図解的表現)
[ statusCode ]
↓
200 ですか? ───> はい ──> ‘OK’ を返す
↓ いいえ
400 ですか? ───> はい ──> ‘Bad Request’ を返す
↓ いいえ
500 ですか? ───> はい ──> ‘Internal Server Error’ を返す
↓ いいえ
[ _ (ワイルドカード) ] ──> ‘Unknown Status’ を返す
(※もしワイルドカードがなく、どれにも一致しない場合は Runtime Error!)
—
4. 比較まとめ:どっちを使うべき?
| 項目 | `if-case` | `switch式` |
| :— | :— | :— |
| 主な用途 | 特定の形にハマるデータだけサクッと処理したいとき | 入力値に応じた「変換」や「網羅的な分岐」をしたいとき |
| マッチ失敗時の挙動 | ブロックをスキップして何もしない | エラー(StateError)になる(ワイルドカードが必須) |
| 値の返却 | 返さない(文であるため) | 必ず値を返す(式であるため) |
設計指針:意図しないスキップを防ぐために
1. 「一部のデータだけ特別扱いして処理したい」場合
⇒ 安全にスルーしてくれる `if-case` を使います。ただし、マッチしなかった場合の「else」抜けに注意してください。
if-case json case {‘name’: String name} {
print(name);
} else {
// マッチしなかった場合のフォールバックを明示的に書くと安全!
print(‘Invalid JSON structure’);
}
2. 「すべてのパターンを確実にハンドリングして値に変換したい」場合
⇒ `switch式` を使い、必ずワイルドカード `_` や網羅的なパターンを用意して、予期せぬ失敗(クラッシュ)を防ぎます。
—
まとめ
いかがでしたでしょうか?
Dartのパターンマッチングにおける「失敗」の扱いは、一見すると厳しく感じるかもしれませんが、言語仕様として「バグを隠さない」ための非常に洗練された仕組みです。
- `if-case` は 「失敗したらスルーする(寛容)」
- `switch式` は 「失敗(網羅漏れ)させないために網羅性を強要する(厳格)」
この違いを頭に置いておけば、もう複雑なデータ構造を扱うコードも怖くありません。
ぜひ明日のFlutter/Dart開発で使ってみてくださいね。あなたのコードがより堅牢で美しくなるはずです!