こんにちは!FlutterやDartを使った開発を楽しんでいますか?
他のプログラミング言語、例えばJavaScriptやRust、Pythonなどを触ったことがある方なら、「パターンマッチングって便利だな〜」と感じたことがあるかもしれません。Dart 3で導入されたパターンマッチングは、私たちのコードを劇的に美しく、そして堅牢にしてくれました。
今回はその中でも、リストの先頭や末尾をスマートに抜き出しつつ、残りの要素をごっそりまとめて受け取る「可変長マッチング(restパターン:`…`)」について、深く掘り下げていきましょう。
ここをクリアすれば、Dartのリスト操作の引き出しが一気に広がり、コードを書くのがもっと楽しくなりますよ。一緒にマスターしていきましょう!
—
1. Dart 3のリストパターンと「restパターン(…)」の基本
まずは、リストパターンがどのようなものか、イメージを掴みましょう。
Dartでは、`switch`文や`if-case`文、あるいは変数宣言の代入(destructuring)において、リストの構造をそのままパターンのように当てはめて値を切り出すことができます。
その中で、「要素の数が可変(何個あるかわからない)」な残りの部分を受け取るために使うのが、ドット3つ(`…`)で表されるrestパターンです。
図解イメージ:リストの解体職人
[ ターゲットのリスト ] —> [ 先頭(first) ] [ 残りすべて(rest) ]
[1, 2, 3, 4, 5] —> 1 [2, 3, 4, 5] (restパターン)
コードで見ると、次のように書きます。
void main() {
final numbers = [10, 20, 30, 40, 50];
// 先頭の1つを取り出し、残りを rest 変数にまとめる
// 構文: [ 変数名, …残りを格納する変数名 ]
final [first, …rest] = numbers;
print(‘先頭: $first’); // 出力: 10
print(‘残り: $rest’); // 出力: [20, 30, 40, 50] (これは新しい List
}
非常にシンプルですよね!`first`には最初の要素である`10`が入り、`rest`には残りの要素が新しいリストとして切り出されます。
—
2. 実践的ユースケース:リストの先頭・末尾・中身の動的ハンドリング
実際のアプリケーション開発(例えばFlutterのUIビルダーや、APIレスポンスのJSONパースなど)では、「リストの要素数が可変だが、特定のポジションを処理したい」という場面がよくあります。
restパターンは、先頭だけでなく、末尾や中間など、どこにでも配置できるのが強力なポイントです。
パターンA:末尾の要素を取り出す
「最後の要素だけ特別に処理して、残りはループで回したい」というケースです。
void processLogEntries(List
// 末尾を last に、残りを earlierLogs に分ける
if (logs case […var earlierLogs, var last]) {
print(‘通常のログ数: ${earlierLogs.length}’);
print(‘最新のエラー/重要ログ: $last’);
} else {
print(‘ログが空です’);
}
}
void main() {
processLogEntries([‘起動’, ‘データ取得’, ‘描画完了’, ‘例外発生!’]);
// 出力:
// 通常のログ数: 3
// 最新のエラー/重要ログ: 例外発生!
}
パターンB:先頭と末尾を同時に剥ぎ取る(サンドイッチ型)
「最初と最後は確定しているけれど、中の要素数は可変」という、パーサーやプロトコル処理でよく使うテクニックです。
void handleProtocol(List
// 先頭をヘッダー、末尾をフッター、中身をペイロードとして一網打尽にする
switch (packet) {
case [int header, …var payload, int footer]:
print(‘Header: $header’);
print(‘Payload: $payload’);
print(‘Footer: $footer’);
default:
print(‘無効なパケット構造です’);
}
}
void main() {
handleProtocol([0xFF, 1, 2, 3, 0xEE]);
// 出力:
// Header: 255
// Payload: [1, 2, 3]
// Footer: 238
}
このように、パターンの形がそのままデータの構造を表現するため、コードの意図がひと目で伝わります。
—
3. 陥りやすい文法エラーと知っておくべき注意点
非常に便利なrestパターンですが、Dartのコンパイラ(AOT/JIT)が厳格にチェックするため、いくつか「やってはいけない書き方」があります。ここで躓きやすいポイントを押さえておきましょう。
注意点1:restパターンは1つのリストパターンにつき「1つだけ」
「残りの部分」は当然1つしか存在し得ないため、1つの大カッコ `[…]` の中に複数置くことはできません。
void badExample(List
// ❌ コンパイルエラー! restパターン (…) は複数使えません
// final [first, …middle, …last] = list;
}
もし中間を切り出したい場合は、位置を固定するか、複数回に分けてパターンマッチングを行う必要があります。
注意点2:要素数不足によるランタイムエラー(推論とマッチ失敗)
変数の代入(`final […] = …`)でパターンマッチを行う際、リストの要素数がパターンに満たない場合は、コンパイル時ではなく実行時(Runtime)に `StateError` や `FormatException`(MatchError)が発生します。
void main() {
final List
// 実行時エラー:要素が最低1つ(first用)必要なのに空のためマッチに失敗する
// final [first, …rest] = emptyList;
}
安全に処理したい場合は、変数代入ではなく、`if-case`文や`switch`文を使いましょう。これらはマッチしなかった場合に安全に `else` 節や次のケースにフォールバックできます。
void safeHandle(List
if (list case [int first, …var rest]) {
print(‘安全に処理できました: $first’);
} else {
print(‘リストが空なので安全にスキップします’);
}
}
—
4. まとめ:Dartを掌握する極限の知見
今回はDart 3のリストパターンにおける「可変長マッチング(restパターン)」について解説しました。
- `[first, …rest]` のように記述し、先頭や末尾を切り出しつつ残りをリストとして扱える。
- `…` はリスト内のどこ(先頭・中間・末尾)にでも配置可能。ただし、1つのパターンにつき1箇所のみ。
- 安全性を担保するため、リストの要素数が保証できないコンテキストでは代入ではなく `if-case` や `switch` を活用する。
リスト操作のためにわざわざ `.sublist(1)` を書いたり、インデックスを指定して安全性を気にするボイラープレートコードとは、今日でお別れです。
ここをクリアできれば、あなたのDartコードはより宣言的で、洗練されたものになります。ぜひ、日々の開発に取り入れてみてくださいね!