【入門編】Dartの「リストパターン」における可変長マッチング:restパターン(…)の内部実装 – Dart コア文法・オブジェクト指向・Null安全解析バイブル

こんにちは!FlutterやDartの開発現場で、日々のコードをより美しく、そして強固なものにしようと奮闘している皆さん。先輩フルスタックエンジニアの私です。

今回は、Dart 3で導入された「パターンマッチング」、その中でも特に強力な「リストパターン(List Patterns)」、そしてその肝である可変長マッチング(restパターン: `…`)について深く掘り下げていきます。

「リストの先頭と末尾の要素を同時にサクッと取り出したい」
「真ん中の長さがどう変動しても、綺麗に安全にデータを分解したい」

そんな現場の悩みを一発で解決できるのがリストパターンです。ここをクリアすれば、あなたのDartのコードは一段と洗練されますよ。さあ、一緒にマスターしていきましょう!

—

1. リストパターンと restパターン(`…`)の基本

これまでのDartでは、リストの要素を取り出すときには `list[0]` とインデックスを指定したり、`sublist` を使ったりしていましたよね。でも、それだとコードが冗長になりがちで、リストの長さが変わったときのバグの温床にもなりました。

Dart 3のリストパターンを使えば、まるでリストの構造をそのまま「型取り」するかのように、直感的に変数をバインドできます。

まずは基本的な使い方を見てみましょう。

void main() {
// 3つの要素を持つリスト
final numbers = [10, 20, 30];

// リストパターンによる分解
// 変数 a, b, c にそれぞれの位置の要素が代入されます
var [a, b, c] = numbers;

print(‘a: $a, b: $b, c: $c’); // 実行結果: a: 10, b: 20, c: 30
}

非常にシンプルですね。しかし、実際の開発現場では「リストの長さが固定ではない」ケースがほとんどです。「先頭の1つと、最後の1つだけ欲しい。間の要素は何個あってもいい(あるいはまとめて扱いたい)」というときに登場するのが、restパターン(`…`)です。

restパターンの基本的な使い方

void main() {
final scores = [95, 80, 85, 90, 75];

// 先頭と末尾、そして「間のすべて」を restパターン でキャプチャする
var [first, …, last] = scores;

print(‘最高/最初のスコア: $first’); // 95
print(‘最後のスコア: $last’); // 75
}

ここで `…` が出てきました。これが restパターンです。
「先頭の要素を `first` に、最後の要素を `last` にマッチさせ、その間にある残りの要素(可変長)はすべて無視する(または別のリストとして受け取る)」という指定が、たった1行でできてしまいます。

—

2. 間にある要素をリストとして受け取る(名前付き restパターン)

ただ捨てるだけではなく、間の要素をごっそり新しいリストとして受け取りたい場合もありますよね。そんなときは、`…` に変数名をつけます(これを名前付き restパターンと呼びます)。

void main() {
final pathSegments = [‘api’, ‘v1’, ‘users’, ‘profile’, ‘edit’];

// 先頭をルート、末尾をアクション、中間をすべてパスのセグメントとして取得
var [root, …subPaths, action] = pathSegments;

print(‘ルート: $root’); // api
print(‘中間セグメント: $subPaths’); // [v1, users, profile]
print(‘アクション: $action’); // edit
}

おぉ、これは便利だと思いませんか?
URLのルーティング解析や、コマンドライン引数のパースなどで、この書き方を知っているだけでコードの量が半分以下になります。

—

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

非常に強力な restパターンですが、Dartのコンパイラを正しく唸らせるためには、いくつか守るべきルール(制約)があります。現場でよくやりがちなエラーを事前に把握しておきましょう。

エラー1: restパターンは1つのリストパターンに「1つだけ」しか置けない

「先頭のほうでも `…` を使って、末尾のほうでも `…` を使いたい」と思うかもしれませんが、それは許されません。

void main() {
final data = [1, 2, 3, 4, 5];

// ❌ コンパイルエラー!
// restパターン (…) は1つのリストパターンにつき1箇所しか配置できません。
// var […beginning, middle, …ending] = data;
}

理由: コンパイラや実行時(Dart VM)が、「どこからどこまでが最初の rest で、どこからが次の rest なのか」を曖昧さなく確定できないためです。リストのパースは一意に決まる必要があるため、`…` は1つだけと決まっています。

エラー2: 固定長要素との組み合わせ順序

restパターンを配置する場所は、先頭、中間、末尾のどこでも可能ですが、型や長さの制約に合致している必要があります。

void main() {
final numbers = [1, 2, 3];

// ❌ 実行時例外(FormatException / StateErrorの類)になる可能性
// 要素が3つしかないのに、以下のように厳密に要素数を指定しすぎると
// マッチ失敗(Subpattern matching failed)エラーになります。
// var [a, b, c, d, …rest] = numbers;
}

リストの実際の長さがパターンの要求(この場合は最低4つ必要)を満たしていない場合、Dartは `Null` を返すのではなく、実行時エラー(MatchError)をスローします。
そのため、リストの長さにゆとりがあるか、あるいは `if (numbers case [var a, …])` のように `switch` や `if-case` 構文と組み合わせて安全にチェックするのがベストプラクティスです。

—

4. 現場で使える! `if-case` と組み合わせた堅牢なコード

実際の開発では、リストの長さが保証されていない外部からのデータ(APIのレスポンスやJSONのリストなど)を扱うことが多いはずです。そんなときは、`if (value case pattern)` を使って安全にガードしましょう。

// APIから取得したログデータのリストを想定
void processLogs(List logs) {
// 安全なパターンマッチングによる安全弁
if (logs case [var level, …var messages, ‘EOF’]) {
print(‘ログレベル: $level’);
print(‘メッセージ数: ${messages.length}件’);
print(‘正常に終端(EOF)に到達しました。’);
} else {
print(‘不正なログフォーマットです。’);
}
}

void main() {
processLogs([‘INFO’, ‘DB接続成功’, ‘キャッシュロード完了’, ‘EOF’]);
// 出力:
// ログレベル: INFO
// メッセージ数: 2件
// 正常に終端(EOF)に到達しました。

processLogs([‘ERROR’, ‘タイムアウト’]);
// 出力: 不正なログフォーマットです。
}

このように `if-case` と restパターンを組み合わせることで、データの構造検証と変数への抽出を同時に、かつ安全に行うことができます。

—

まとめ

今回は Dart 3 のリストパターンと、可変長マッチングを実現する restパターン(`…`)について解説しました。

  • 先頭と末尾の同時抽出: インデックスアクセスを使わずに美しく記述できる。
  • 名前付き restパターン: 中間の要素をごっそりリストとして回収できる。
  • ルール: `…` は1つのリストパターンに1つまで。リスト長が合わないと実行時エラーになるため、`if-case` で安全にハンドリングする。

ここをクリアすれば、あなたのDartの基本はバッチリマスターできていますよ!
複雑なデータ構造のハンドリングに自信を持って、明日のコーディングを楽しんでいきましょう。それではまた!

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