Goのループのキーワードは for の1つだけです。形は4つあり、それらで他の言語の for、while、do while、foreach で書くループをすべてまかないます。
ループのヘッダーをかっこで囲むことはなく、波かっこは常に必要です。
3つの節からなるループ
for init; condition; post {
// body
}
initはループの前に一度だけ実行されます。たいていはi := 0のような短い変数宣言で、その変数はループの中にだけ存在します。conditionは毎回の反復の前にチェックされます。偽になるとループが終わります。- 本体が実行されます。
postは各反復の後に実行されます。典型的にはi++です。
Goでは i++ は式ではなく文なので、post に i++, j-- とは書けません。代わりに多重代入を使います:i, j = i+1, j-1。
3つの節はどれも省略できます。for ; i < 10; {} も正しく、gofmt はこれを for i < 10 {} に書き換えます。これが次の形です。
条件だけのループ(Goのwhile)
条件だけを書くと、for は他の言語の while のように振る舞います。
別に do while はありません。「少なくとも1回は実行する」挙動の作り方はwhileループのページで紹介しています。
無限ループ
後ろに何も書かない for は永遠にループします。break、return、os.Exit、またはpanicで抜けます。
for {
line, err := reader.ReadString('\n')
if err != nil {
break
}
process(line)
}
これはサーバー、ワーカー、入力ループの標準的な形です。本体が空の for {} はCPUコアを1つ使い切ります。実際のコードでは、本体が何か(読み取り、チャネルからの受信、select)でブロックします。
rangeループ
range はコレクションを走査し、反復ごとに1つまたは2つの値を返します。それが何になるかは、何に対してrangeするかで決まります。
| rangeの対象 | 1つ目の値 | 2つ目の値 |
|---|---|---|
| スライスまたは配列 | インデックス | 要素(コピー) |
| 文字列 | バイトインデックス | ルーン(Unicodeの文字) |
| マップ | キー | 値 |
| チャネル | 要素 | (なし) |
整数 n(Go 1.22) | 0からn-1 | (なし) |
| イテレータ関数(Go 1.23) | 関数が渡すもの | 関数が渡すもの |
出力からわかる細かい点が3つあります。
- 値の変数は コピー です。ループの中で
v *= 2としてもスライスは変わりません。要素を変更するには、3つ目のループのようにnums[i]でスライスにインデックスを使います。 - 文字列のインデックスは バイト オフセットです。
éは2バイトを使うので、インデックスは1から3に飛びます。 - マップの順序は意図的に ランダム です。コードが順序に依存できないように、Goがランダムにしています。
slices.Sorted(maps.Keys(m))(Go 1.23)でキーを順に取り出せます。
チャネル、イテレータ関数、コピーのルールを含め、range の全体はrangeのページで扱っています。
breakとcontinue
break は最も内側のループを終わらせます。continue は次の反復に飛びます(3つの節のループでは先に post を実行します)。
ラベル付きのbreakとcontinue
普通の break は最も内側のループしか抜けません。ネストしたループの中から外側のループを抜けるには、外側のループにラベルを付けます。
ラベルは識別子の後にコロンを付けたもので、for の直前に置きます。使われていないラベルは、未使用の変数と同じくコンパイルエラーとして報告されます。
switchとselectの中のbreak
for の中で、switch や select のケースにある break が抜けるのはループではなく switch や select です。
for {
switch cmd := next(); cmd {
case "quit":
break // only exits the switch; the loop keeps running
}
}
switch や select の中からループを抜けるには、ラベル付きの break か return を使います。
Go 1.22以降のループ変数
Go 1.22でループ変数のスコープが変わり、反復ごとに i と v の 新しいコピー が作られるようになりました。以前はループ全体で1つの変数が使い回されていたので、それを捕捉したクロージャやゴルーチンが壊れていました。
Go 1.22以降では、これは 0 1 2 と [aa bb cc] を表示します。古いセマンティクスでは、同じプログラムを for i := 0; i < 3; i++ で書くと(整数に対するrangeは1.22より前には存在しませんでした)3 3 3 と表示され、ゴルーチンは共有の i と name で競合していました。ループ本体の先頭に i := i と書く昔の回避策は、もう必要ありません。
この挙動はコンパイラのバージョンではなく、go.mod の go 行によってモジュールごとに決まります。go 1.21 のままのモジュールは、Go 1.24でビルドしても古いセマンティクスのままです。
よくある間違い
コピーを変更している。 for _, v := range items { v.Count++ } はコピーを変更しています。items[i].Count++ を使うか、ポインタのスライスに対してrangeします。
rangeしながらappendする。 range はスライスを最初に一度だけ評価します。ループ内で追加した要素は走査されないので、ループはちゃんと終わりますが、新しい要素は見えません。
前向きのループでインデックスを使ってスライスから削除する。 要素 i を削除すると残りが左にずれるので、次の要素が飛ばされます。新しいスライスを作るか、後ろからループするか、slices.DeleteFunc を使います。
<=による1つずれ。 for i := 0; i <= len(s); i++ は最後の反復で s[len(s)] を読み、index out of range でpanicします。< を使うか、もっと良いのは range を使うことです。
条件の中の重い処理。 条件は毎回の反復で実行されます。for i := 0; i < expensiveCount(); i++ は毎回その関数を呼ぶので、ループの前に一度だけ計算します。
浮動小数点数をカウンタに使う。 0.1 は2進数で正確に表せないので、for x := 0.0; x != 1.0; x += 0.1 は終わりません。整数で数え、そこから浮動小数点数を計算します。
よくある質問
Goでforループはどう書きますか?
古典的な形は、セミコロンで区切った3つの部分からなり、かっこは付けません。
for i := 0; i < 5; i++ {
fmt.Println(i)
}
Go 1.22以降は for i := range 5 とも書け、0から4まで数えます。スライスをたどるには for i, v := range items を使います。
Goにwhileループはありますか?
while キーワードはありませんが、条件だけを書いた for がwhileループです:for n > 0 { n /= 2 }。条件のない for { ... } は無限ループで、break か return で抜けます。
Goでマップをループするには?
for key, value := range m { ... } です。反復の順序は規定されておらず、ランタイムがランダムにするので、ループのたびに変わることがあります。順序に頼ってはいけません。安定した順序がほしいなら、キーを集めて slices.Sort でソートし、ソートしたキーでループします。
Goでネストしたループから抜けるには?
外側のループの前にラベルを置き、break でその名前を指定します:outer: for ... { for ... { if found { break outer } } }。普通の break は最も内側の for、switch、select だけを抜けます。continue outer も同様に、ラベルを付けたループの次の反復に飛びます。
Go 1.22でループ変数について何が変わりましたか?
Go 1.22より前は、for ループの変数はすべての反復で共有される1つだけだったので、ループ内で作ったクロージャやゴルーチンがみな最後の値を見てしまうことがよくありました。Go 1.22以降は反復ごとに独自のコピーが作られるので、クロージャで i や v を捕捉しても期待どおりに動きます。新しい挙動は、モジュールの go.mod が go 1.22 以降を指定している場合に適用されます。