ゼロ値
宣言だけして値を入れない変数にはゼロ値が入ります。
| 型 | ゼロ値 |
|---|---|
| 数値 | 0 |
bool | false |
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順序に影響しやすくなります。パッケージの最低限の登録に留めるのが無難です。
パッケージの初期化順序
パッケージの初期化は次の順序です。
- インポートされたパッケージ(依存関係の順)
- パッケージレベルの変数(宣言順)
init関数(ソースファイル内の出現順)
例:
main が import → config
config が import → log実行順: logの変数 → logのinit → configの変数 → configのinit → mainの変数 → mainのinit → main.main
同一パッケージ内では、コンパイル単位(ファイル)の並びと各ファイル内の出現順に依存します。初期化順に依存する設計は避けます。
mainパッケージの初期化が終わるとmain関数が呼ばれます。