Go言語の制御構文
2026/09/20

if

ifは条件式がtrueのとき、続くブロックを実行します。else ifやelseで条件分岐を継続できます。

if x < 0 {
    return -x
} else if x == 0 {
    return 0
}
return x

条件式の前にセミコロンで区切った初期化文を記述できます。初期化文で宣言した変数のスコープは、続くelse ifやelseを含むif文全体です。ブロックの外からは参照できません。

if n, err := strconv.Atoi(s); err != nil {
    return err
} else {
    fmt.Println(n) // nはここまで有効
}
// fmt.Println(n) // コンパイルエラー

for

繰り返し処理はfor文で記述します。条件式のみ、初期化と後処理を含む3文形式、range節の3通りの書き方があります。

条件式

条件式を指定すると、評価結果がtrueの間ブロックを繰り返します。条件式を省略した場合は無限ループとなり、breakやreturnで終了します。

for n < 100 {
    n *= 2
}
for {
    if done {
        break
    }
}

初期化・条件・後処理

初期化文、条件式、後処理をセミコロンで区切って並べます。

for i := 0; i < 10; i++ {
    fmt.Println(i)
}

Go言語1.22以降、初期化文で宣言したループ変数は反復ごとに新しく作られます。ループ内で定義した関数から参照しても変数は共有されず、各反復の値がそのまま保持されます。

var prints []func()
for i := 0; i < 3; i++ {
    prints = append(prints, func() { fmt.Println(i) })
}
for _, p := range prints {
    p() // 0 1 2
}

range

配列、配列へのポインタ、スライス、文字列、マップ、チャネル、整数、イテレータ関数を走査します。rangeで宣言したループ変数は、Go言語1.22以降は反復ごとに新しく作られます。

配列、配列へのポインタ、スライス、文字列では添字と値、マップではキーと値を受け取れます。マップの走査順序は決まっていません。チャネルでは受信した値の1つのみを受け取ります。

// スライスは添字と値を受け取る
s := []string{"a", "b"}
for i, v := range s {
    fmt.Println(i, v)
}

// マップはキーと値を受け取る。走査順序は決まっていない
m := map[string]int{"x": 1, "y": 2}
for k, v := range m {
    fmt.Println(k, v)
}

// チャネルは受信した値のみを受け取る
ch := make(chan int, 2)
ch <- 10
ch <- 20
close(ch)
for v := range ch {
    fmt.Println(v) // 10 20
}

Go言語1.22以降では、指定した回数だけ繰り返す用途として整数nも走査できます。for i := range nと記述すると0からn-1まで昇順に取り出します。nが0以下の場合は反復しません。

for i := range 3 {
    fmt.Println(i) // 0 1 2
}

Go言語1.23以降では、関数をrangeで走査できます。イテレータ関数の中でyieldを呼び出すたびに、走査する側のforループ本体が1回実行されます。yieldは通常trueを返しますが、走査側のループがbreakなどで終了するとfalseを返します。この戻り値を判定することで、呼び出し側でループが終了したことを検知し、不要な処理が続くのを防ぐことができます。イテレータ関数がリターンすると走査全体が終了します。

// 呼び出し側に値を渡すイテレータ関数
seq := func(yield func(int) bool) {
    for n := 0; n < 3; n++ {
        if !yield(n) {
            return // 呼び出し側のループ終了を検知し、無駄な処理の継続を防ぐ
        }
    }
}

// イテレータ関数をrangeで走査
for n := range seq {
    fmt.Println(n) // 0 1 2
}

イテレータ関数のシグネチャには、値を渡さない形式、1つの値を渡す形式、2つの値を渡す形式の3パターンがあります。

// 値なし
noVal := func(yield func() bool) {
    if !yield() {
        return
    }
}
for range noVal {
    // 反復のみ行う
}

// 1値
singleVal := func(yield func(string) bool) {
    if !yield("hello") {
        return
    }
}
for v := range singleVal {
    fmt.Println(v) // hello
}

// 2値
pairVal := func(yield func(string, int) bool) {
    if !yield("a", 1) {
        return
    }
}
for k, v := range pairVal {
    fmt.Println(k, v) // a 1
}

breakとcontinue

breakは最も内側のfor、switch、selectを終了し、continueは最も内側のforの次の反復へ進みます。入れ子のループで外側を終了したり外側の反復を進めたりするには、対象のfor文にラベルを付けてbreak ラベル名やcontinue ラベル名と指定します。

Outer:
for i := 0; i < 3; i++ {
    for j := 0; j < 3; j++ {
        if j == 1 {
            continue // 内側ループの次の反復へ進む
        }
        if j == 2 {
            break // 内側ループだけを終了して外側の次のiへ進む
        }
        if i == 1 {
            break Outer // 外側ループごと終了する
        }
        fmt.Println(i, j)
    }
}
// 0 0 を出力して終了

switch

switchは式の値に一致したcaseの本体を実行します。一致するcaseがなくdefault節があるときは、その処理を実行します。default節を省略し、どのcaseにも当てはまらないときは何もしません。1つのcaseにカンマ区切りで複数の値を並べると、いずれかに一致したときに実行されます。

switch day {
case "Mon":
    fmt.Println("月曜")
case "Tue", "Wed":
    fmt.Println("火〜水")
default:
    fmt.Println("その他")
}

各節の処理が終わると自動的にswitch文を抜けます。後続の節へ処理を継続したい場合のみ、節の末尾にfallthroughを記述します。fallthroughは次のcaseの条件判定を行わずに直後の本体を実行します。最後の節には記述できません。

switch rank {
case 1:
    fmt.Println("金")
    fallthrough
case 2:
    fmt.Println("銀")
case 3:
    fmt.Println("銅")
}
// rankが1のとき: 金と銀を出力
// rankが2のとき: 銀を出力
// rankが3のとき: 銅を出力

判定式を省略したswitchは、各caseの条件式を上から順に評価し、最初にtrueとなった節を実行します。

switch {
case x < 0:
    fmt.Println("負")
case x == 0:
    fmt.Println("ゼロ")
default:
    fmt.Println("正")
}

式の前にセミコロンで区切った初期化文を記述できます。宣言した変数のスコープはswitch文全体です。

switch n, err := strconv.Atoi(input); {
case err != nil:
    fmt.Println("invalid")
case n == 0:
    fmt.Println("zero")
default:
    fmt.Println(n) // nはswitch全体で有効
}
// fmt.Println(n) // コンパイルエラー

型スイッチ

インターフェース値の動的型で分岐します。switch v := x.(type)で導入した変数vは、節ごとに暗黙のブロック内で別々に宣言されます。単一の型を指定した節ではvはその型として扱われ、複数の型を並べた節やdefault節では元の値xと同じ型になります。型スイッチではfallthroughを使用できません。

func describe(x any) {
    switch v := x.(type) {
    case int:
        fmt.Println("int", v)    // vはint型
    case string:
        fmt.Println("string", v) // vはstring型
    default:
        fmt.Println("other", v)  // vは元のxと同じany型
    }
}