こんにちは!Dartの奥深い世界へようこそ。世界最高峰のDartコアコミッターである私が、今日は皆さんと一緒にDartの制御構文、特に「ラベル付きbreak/continue」の本質について紐解いていきますね。
他の言語(JavaScriptやJavaなど)を触ってきた方なら、「ラベル?なんだか古臭いし、スパゲッティコードの温床になりそう…」なんて身構えてしまうかもしれません。でも、安心してください。Dart 3のモダンな世界においても、この機能は「正しく使えばコードの意図を最も美しく表現できる防波堤」になります。
ここをクリアすれば、複雑なループ制御も怖くありません。一緒にバッチリマスターしていきましょう!
—
1. ラベル付きbreak/continueってなに?(基本のキ)
通常の `break` や `continue` は、自分自身が一番近くにいる「単一のループ(`for`や`while`)」を脱出したり、次の周回にスキップしたりしますよね。
では、「2重、3重のネストされたループの最深部から、一番外側のループまで一気に抜け出したい」ときはどうでしょう?
フラグ変数(`bool found = false;` など)を用意して、外側のループで毎回そのフラグをチェックしていませんか?
// 良くあるアンチパターン:フラグ変数を使った多重ループの脱出
bool isMatch = false;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (i == 1 && j == 1) {
isMatch = true;
break; // これだと内側のループしか抜けられない!
}
}
if (isMatch) break; // 外側のループを抜けるために毎回チェックが必要
}
なんだかコードが冗長で、本来やりたいこと(探索と脱出)が見えにくくなってしまいますよね。
ここで登場するのがラベル(Label)です。ループの直前に `名前:` と記述することで、そのループブロック全体に名前をつけることができます。
構文のイメージ
outerLoop: // ← これがラベルです
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (/ 条件 /) {
break outerLoop; // 一気に outerLoop の外までジャンプ!
}
}
}
コンパイラやDart VMの視点から言えば、これは複雑な条件分岐のフラグ管理を排除し、バイトコードレベルでジャンプ命令(JMP)を的確に発行するための非常にクリーンな言語機能なんです。
---
2. 実際のコードで動きを見てみましょう
百聞は一見に如かず。具体的なシチュエーションとして、2次元のグリッド座標から特定のアイテムを探し出し、見つかった瞬間にすべての探索を終了するコードを見てみましょう。
void main() {
// 3×3のグリッド(2次元配列)をイメージしてください
final grid = [
[‘A’, ‘B’, ‘C’],
[‘D’, ‘X’, ‘F’], // ここに目当ての ‘X’ があります
[‘G’, ‘H’, ‘I’],
];
print(‘=== 探索開始 ===’);
// searchOuter というラベルを外側のループに付与します
searchOuter:
for (int row = 0; row < grid.length; row++) {
for (int col = 0; col < grid[row].length; col++) {
final item = grid[row][col];
print('検査中: 座標($row, $col) -> $item’);
if (item == ‘X’) {
print(‘目当ての “X” を座標($row, $col)で発見しました!’);
// 内側のループから、一気に searchOuter の外へ脱出!
break searchOuter;
}
}
}
print(‘=== 探索終了 ===’);
}
実行結果
=== 探索開始 ===
検査中: 座標(0, 0) -> A
検査中: 座標(0, 1) -> B
検査中: 座標(0, 2) -> C
検査中: 座標(1, 0) -> D
検査中: 座標(1, 1) -> X
目当ての “X” を座標(1, 1)で発見しました!
=== 探索終了 ===
どうでしょう? `X` を見つけた瞬間に、内側・外側のループの垣根を越えて、一気に `=== 探索終了 ===` まで制御がジャンプしているのが分かりますよね。無駄な走査が一切発生していません。
—
3. `continue` のラベルはどう動く?
`break` が「ループを抜ける」のに対し、`continue` は「指定したラベルのループの次の周回に進む」という挙動をします。こちらも実例を見てみましょう。
void main() {
// 外側のループに outerLoop ラベルを貼る
outerLoop:
for (int i = 1; i <= 3; i++) {
for (int j = 1; j <= 3; j++) {
// i と j が同じ値の時は、外側のループの次の周回(iをインクリメント)に進む
if (i == j) {
print('--- i=$i, j=$j なので outerLoop の continue を実行 ---');
continue outerLoop;
}
print('処理実行: i=$i, j=$j');
}
}
}
実行結果
処理実行: i=1, j=2
処理実行: i=1, j=3
— i=2, j=2 なので outerLoop の continue を実行 —
処理実行: i=3, j=1
処理実行: i=3, j=2
— i=3, j=3 なので outerLoop の continue を実行 —
`i == j`(1と1、2と2、3と3)のタイミングで、内側のループの残りの処理がスキップされ、外側の `i` のループが次のステップに進んでいるのが確認できますね。
—
4. 陥りやすい罠と「正しい使い所」の黄金律
ここまで読むと「すごく便利だから色々なところで使おう!」と思われるかもしれませんが、ちょっと待ってください。ここからがチーフアーキテクトからの重要なアドバイスです。
陥りやすい文法エラー・ミス
1. 存在しないラベルを指定する
for (int i = 0; i < 3; i++) {
break nonExistentLabel; // 🔴 コンパイルエラー: Target of break not found: 'nonExistentLabel'
}
Dartは静的型付け言語です。定義されていないラベルを指すことはできません。
2. ループ以外の場所にラベルを貼る(またはジャンプする)
Dartのラベル付き `break`/`continue` は、必ずループ(`for`, `while`, `do-while`)に対してのみ使用できます。C言語のような任意の場所への `goto` 文としての使い方はできません(これは意図的な言語仕様の制限であり、コードの安全性を保つための素晴らしい決断です)。
可読性を損なわないための適切な適用範囲
ラベル付き `break` は強力ですが、多用するとコードの実行フローを追いづらくする「諸刃の剣」です。以下のガイドラインを守ることで、美しいコードを保てます。
- 適用範囲の目安:最大でも2重ループまでにとどめる
3重、4重のループが必要になる時点で、アルゴリズム自体の設計や、処理を小さな関数(メソッド)に分割すべきサインです。
- 「早期リターン(Early Return)」としての関数抽出と比較する
多重ループを関数の中に閉じ込めてしまい、見つかったら `return` するというアプローチの方が、現代のDart/Flutter開発では好まれることが多いです。
// 推奨される別アプローチ:関数に切り出して return する Dartが持つこうした細かな言語機能を正しく理解し、適材適所で使い分けられるようになると、あなたの書くコードの質は一段も二段も跳ね上がります。 さあ、今日の学びを活かして、あなたのDartコードをもっとエレガントに書いてみましょう。FlutterやDartのマスターへの道は、こうした足元の一歩一歩の積み重ねです。応援していますよ!
Point? findTarget(List> grid) {
for (int row = 0; row < grid.length; row++) {
for (int col = 0; col < grid[row].length; col++) {
if (grid[row][col] == 'X') {
return Point(row, col); // 関数ごと抜けるのでシンプル!
}
}
}
return null;
}
関数に切り出すほどでもない、あるいはパフォーマンスやスコープの都合上その場でサクッと多重ループを抜けたい、というピンポイントな場面においてのみ、ラベル付き `break` は最高の輝きを放ちます。
---
まとめ