【入門編】Dart 3.0以降のパターンマッチングを活用した、変数宣言時の型ガードと分解 – Dart コア文法・オブジェクト指向・Null安全解析バイブル

こんにちは!Dartの世界へようこそ。
Flutterを使ったアプリ開発や、モダンなDartでのバックエンド開発を楽しんでいますか?

他のプログラミング言語(TypeScript、Swift、Rustなど)を触ったことがある方なら、「おっ!」と思うかもしれませんが、Dart 3.0以降、変数宣言や条件分岐の世界が一気にモダンでパワフルになりました。それが今回テーマにする「パターンマッチングによる型ガードと分解(Destructuring)」です。

「難しそう……」なんて身構えなくて大丈夫ですよ。ここをクリアすれば、あなたのDartコードは一気に洗練され、バグの入り込む隙がなくなります。一緒に一歩ずつ、本質を紐解いていきましょう!

—

1. そもそも「パターンマッチングと分解」ってなに?

これまでのプログラミングでは、例えば「APIから返ってきたユーザー情報(名前と年齢)」を扱うとき、こんな風に書いていませんでしたか?

// 昔ながらの泥臭い書き方
var user = fetchUser();
String name = user[‘name’] as String;
int age = user[‘age’] as int;

これだと、データ構造が変わったときにキャストエラー(`TypeError`)が起きたり、コードが冗長になったりしますよね。

Dart 3.0で導入された分解(Destructuring)を使うと、構造を持ったデータ(レコードやオブジェクト)を、まるで箱を開けるように一気に変数へバラして代入できるようになります。

// Dart 3以降のスマートな書き方
var (name, age) = fetchUserRecord();

これだけでも十分便利なんですが、さらに「パターンマッチング(型ガード)」を組み合わせることで、「このデータがこういう型や値の形をしている時だけ、安全に取り出して使う」という高度な安全地帯を作ることができるんです。

—

2. 基本の使い方:レコードの分解と型ガード

まずは、異なる型のデータが混ざった「レコード(Record)」を安全に受け取る基本の形を見てみましょう。

実践コード例

void processResponse(Record response) {
// switch式とパターンマッチングを使った「型ガード」の例
String message = switch (response) {
// パターン1: 成功時 (int型のステータスコードと、String型のデータ)
(int code, String data) when code == 200 => ‘成功: $data’,

// パターン2: エラー時 (int型のエラーコードと、String型のメッセージ)
(int code, String errorMsg) when code >= 400 => ‘エラー[$code]: $errorMsg’,

// パターン3: 想定外のデータ構造
_ => ‘未知のレスポンスです’,
};

print(message);
}

void main() {
// テストデータの実行
processResponse((200, ‘ユーザーデータの取得に成功しました’));
// 出力: 成功: ユーザーデータの取得に成功しました

processResponse((404, ‘お探しのページは見つかりません’));
// 出力: エラー[404]: お探しのページは見つかりません
}

コードのここがポイント!

  • `switch (response)` の網羅性: Dartのswitch式は非常に賢く、考えられるすべてのパターンをカバーしているかをコンパイラがチェックしてくれます。
  • `when` 節(ガード条件): 型が一致した上で、さらに細かい条件(`code == 200` など)で絞り込むことができます。これが型ガードの本質です。
  • `_`(ワイルドカード): 「上記以外すべて」にマッチする安全網です。

—

3. 変数宣言時のスマートな型ガード:`if-case`

「switchを書くほどでもないけれど、この変数を受け取る時に、特定の型や構造であることを保証して安全にバラしたい!」という場面、よくありますよね。

そんな時は、`if-case`文の出番です。

void handleInput(Object userInput) {
// userInputが 「Stringのコマンド」かつ「特定のキーワード」の時だけ分解・実行
if (userInput case (String command, int retryCount)) {
// ここに入った瞬間、変数 command は確実に String、retryCount は int として扱えます!
print(‘コマンド: $command (リトライ回数: $retryCount)’);
} else {
print(‘無効な入力フォーマットです。’);
}
}

void main() {
handleInput((‘sync’, 3));
// 出力: コマンド: sync (リトライ回数: 3)

handleInput(‘ただの文字列’);
// 出力: 無効な入力フォーマットです。
}

コンパイラの裏側の話(ここがDartコミッターの推しポイント!)

Dartは静的型付け言語です。`if-case` を通過したブロック内では、Dart VM(およびAOTコンパイラ)はフロー解析(Flow Analysis)によって、その変数の型と構造が確定していることを完全に把握しています。
そのため、実行時に無駄な型チェックや安全でないキャストが走らず、ネイティブコードにコンパイルされた際も極めて高速に動作します。安全性とパフォーマンスが両立している最高の瞬間です。

—

4. 陥りやすい文法エラーと注意点

初心者のうちは、強力な機能ゆえに少しだけハマりやすいポイントがあります。代表的なものをいくつか挙げておきますね。

エラー①:可変(`var`)と不変(`final` / `const`)の混同

分解して代入する際、それぞれの変数に個別の修飾子はつけられません。全体に対して `var` や `final` を指定します。

// ❌ やってはいけない書き方
(var String name, int age) = (‘Alice’, 20); // コンパイルエラー

// ⭕ 正しい書き方(全体を final や var にする)
final (name, age) = (‘Alice’, 20);

エラー②:型不一致による実行時例外(パターンの不成立)

`if-case` ではなく、単に `=` で分解代入する際、実際のデータ構造がパターンと一致しないと、コンパイルは通っても実行時に例外(`FormatException`)が発生します。

// データが想定と違う構造だった場合
var (String name, int age) = (123, ‘Alice’); // 💥 実行時エラー!

対策: 確実に形が保証できない外部からのデータ(JSONやAPIレスポンスなど)を受け取る時は、必ず `if-case` や `switch` を使って安全にガードしましょう。

—

まとめ

いかがでしたでしょうか?

  • 分解(Destructuring)で、データを取り出すコードが劇的にスッキリする
  • パターンマッチングと `if-case` / `switch` を使えば、実行時安全性を担保した「型ガード」がスマートに書ける
  • コンパイラのフロー解析により、安全かつ高速に実行される

ここをクリアできれば、あなたのDartの基礎力はもう中級者の領域に達しています!
ぜひ明日のコードから、無駄なキャストを捨てて、モダンなパターンマッチングを取り入れてみてくださいね。

それでは、また次回の記事でお会いしましょう。ハッピー・コーディング!

タイトルとURLをコピーしました