Go言語の字句・宣言とスコープ
2026/07/28

ソースコードの表現

GoのソースコードはUTF-8でエンコードされたUnicodeテキストです。大文字と小文字は別の文字として扱われます。

コメント

コメントは、行コメント(//から行末まで)とブロックコメント(/*から*/まで)の2種類です。

// 行コメント

/*
ブロックコメント
*/

トップレベル宣言の直前に改行なしで置かれたコメントは、その宣言のドキュメントとみなされます。

// Add はaとbの和を返す。
func Add(a, b int) int {
    return a + b
}

識別子とキーワード

識別子は、変数・関数・型・パッケージなどに付ける名前です。文字で始まり、文字と数字からなります。アンダースコア_は、仕様上「小文字の文字」として扱われます。そのため識別子の先頭にも使え、先頭が_の名前は小文字始まりと同じく他パッケージからは見えません(公開されるのは先頭がUnicodeの大文字のときだけです)。

var _count int // 先頭が_なのでパッケージ外からは見えない
var count int  // 先頭が小文字でも同様に見えない
var Count int  // 先頭が大文字なら他パッケージから見える

次の語はキーワードであり、識別子には使えません。

break default func interface select
case defer go map struct
chan else goto package switch
const fallthrough if range type
continue for import return var

セミコロンの自動挿入

文法上、Goの文はセミコロンで終わります。字句解析は、コンパイラがソース文字列をif:=などのトークンに分ける処理です。この段階で次のトークンの直後に改行やEOFがあればセミコロンを補うため、ソースに;を書く必要はほとんどありません。ソースに;を書いた場合もそれはセミコロンのトークンとして読まれ、行末が;の行では自動挿入は走らないため、二重にはなりません。

次のトークンの直後の改行でセミコロンが入ります。

  • 識別子、リテラル
  • break, continue, fallthrough, return
  • ++, --, ), ], }

if条件式の直後で改行すると、行頭の{の前にセミコロンが入ったと解釈されコンパイルエラーになります。制御構文の{は同じ行に書きます。

if x < 0 {
    return -x
}
if x < 0
{
    // コンパイルエラー
}

変数宣言

varで型と初期値を明示できます。初期値を省略するとゼロ値が入ります。型を省略すると右辺から推論します。関数内では:=による短い変数宣言も使えます。

var x int = 10
var y = 20        // 型推論でint
z := 30           // 短い変数宣言(関数内のみ)

パッケージ直下では:=は使えず、varが必要です。

package demo

var count = 0 // OK
// n := 1     // コンパイルエラー: パッケージレベルでは:=不可

宣言した変数を使わないとコンパイルエラーになります。意図的に捨てるときは_へ代入します。

x := 1
_ = x

複数宣言

同じブロック内で複数の識別子をまとめられます。

var (
    host = "localhost"
    port = 8080
)

複数の戻り値を、左辺の変数に同時に受け取れます。すでに宣言済みの変数への再代入は=です。

x, y = 1, 2
a, b = b, a

関数から複数の値を受け取るときも、宣言済みなら=で再代入します。

func swap(a, b int) (int, int) { return b, a }

x, y := 1, 2
x, y = swap(x, y) // 2, 1

命名の慣習

複数語の名前はアンダースコアではなくMixedCapsまたはmixedCapsを使います。短いスコープではiokのような短い名前もよく使われます。

ゲッターにはGetプレフィックスを付けない慣習があります。フィールドownerならメソッドはOwnerとします。

ブロック

ブロックは宣言と文のグループです。波括弧{ }で囲みます。関数本体、制御構文の本体、ifの初期化文を含む全体などがブロックを形成します。

スコープ

識別子は宣言されたブロックとその内側のブロックで有効です。内側のブロックで同名を宣言すると、外側の名前はそのブロック内では隠れます。

x := 1
{
    x := 2
    fmt.Println(x) // 2
}
fmt.Println(x) // 1

再宣言と:=

:=は、左辺に少なくとも1つ新しい変数があるときに使います。すでにそのブロックで宣言されている名前には代入され、それ以外は新たに宣言されます。

x, y := 1, 2
x, z := 3, 4 // xは代入、zは新規

ブランク識別子

_はブランク識別子です。主な用途は次の3つです。

  1. 値の破棄: _, err := do()
  2. 副作用のみのimport: import _ "image/png"
  3. コンパイル時の型チェック: var _ io.Reader = (*MyType)(nil)

未使用importはコンパイルエラーです。_でimportすると、パッケージのinitだけが実行され、名前は使いません。

ラベルのスコープ

ラベルは、Outer:のように文の前に付ける名前です。入れ子のループで外側へ抜けるときはbreak Outer、外側ループの次の反復へ進むときはcontinue Outerと書きます。gotoの飛び先にも使います。

Outer:
for i := 0; i < 3; i++ {
    for j := 0; j < 3; j++ {
        if j == 1 {
            continue Outer // 内側を抜け、外側ループの次のiへ
        }
    }
}
i := 0
Again:
    fmt.Println(i)
    i++
    if i < 3 {
        goto Again
    }

ラベルはそれを含む関数内でのみ有効です。別の関数のラベルには飛べません。

ゼロ値

宣言だけして値を入れない変数にはゼロ値が入ります。

ゼロ値
数値0
boolfalse
string""
ポインタ、スライス、マップ、チャネル、関数、インターフェースnil
構造体各フィールドのゼロ値
配列各要素のゼロ値

未初期化変数

Goには「未初期化のローカル変数」がありません。宣言された識別子は常にゼロ値か、明示的な初期化値を持ちます。これにより、読み取り前の不定値によるバグを減らす設計です。

ポインタ・スライス・マップ・チャネル・関数・インターフェースのゼロ値はnilです。nilは型ごとに「まだ使うための内部のデータ構造が用意されていない」状態を表します。操作がその内部構造への読み書きを前提にするとパニックになります。内部構造がなくても意味がある操作や、必要ならこちらで確保する組み込み(appendなど)は動くことがあります。

nilだからどの操作もパニックになるわけではなく、型と操作の組み合わせで決まります。たとえばnilマップへの書き込みはハッシュ表が無いためパニックですが、nilスライスへのappendは要素を格納する配列を新しく確保して動きます。マップを書き込みに使うにはmakeかリテラルでの初期化が必要です。

var s []int
s = append(s, 1) // 要素を格納する配列を確保して動く

var m map[string]int     // m == nil
// m["x"] = 1            // ハッシュ表が無いのでpanic
m = make(map[string]int) // または m = map[string]int{}
m["x"] = 1

定数宣言

定数はコンパイル時に評価できる式に限られます。変数の参照や関数呼び出しは定数式にならず、コンパイルエラーになります。

const Pi = 3.14159
const (
    StatusOK       = 200
    StatusNotFound = 404
)

var limit = 10
// const max = limit              // コンパイルエラー: 変数は定数式に使えない
// const n = len([]int{1, 2, 3})  // コンパイルエラー: スライスの長さは定数にならない
// const now = time.Now()         // コンパイルエラー: 実行時の値は使えない

iota

定数宣言ブロック内でiotaは行ごとに0から増える連番です。新しいconst (ブロックを開くとiotaは0にリセットされます。ファイル全体で増え続けるわけではありません。

const (
    Sunday = iota // 0
    Monday        // 1
    Tuesday       // 2
)

式と組み合わせた例です。各行でiotaの値が先に決まり、その行の式に代入されます。

const (
    _  = iota             // 0を捨てる
    KB = 1 << (10 * iota) // 1024
    MB                    // 1048576
    GB                    // 1073741824
)

パッケージレベルの変数初期化のあと、initが自動実行されます。

init関数

パッケージ内にfunc init()を複数定義できます。initはインポートや変数の初期化のあと、自動的に実行されます。

func init() {
    log.SetFlags(log.LstdFlags)
}

initに重い副作用やグローバル状態の変更を詰め込むと、テストやimport順序に影響しやすくなります。パッケージの最低限の登録に留めるのが無難です。

パッケージの初期化順序

パッケージの初期化は次の順序です。

  1. インポートされたパッケージ(依存関係の順)
  2. パッケージレベルの変数(宣言順)
  3. init関数(ソースファイル内の出現順)

例:

main が import → config
config が import → log

実行順: logの変数 → loginitconfigの変数 → configinitmainの変数 → maininitmain.main

同一パッケージ内では、コンパイル単位(ファイル)の並びと各ファイル内の出現順に依存します。初期化順に依存する設計は避けます。

mainパッケージの初期化が終わるとmain関数が呼ばれます。