定数は、コンパイル時に決まる値です。const で宣言すると、Goはそれを変更しようとするあらゆる試みを拒否します。
定数はパッケージレベルでも関数の中でも宣言でき、1行に1つずつ書いても const ( ... ) ブロックにまとめても構いません。MB と GB は他の定数から計算されており、コンパイラがその計算をビルド時に一度だけ行います。
MaxRetries = 5 のように定数に代入すると、コンパイルエラーになります。
./main.go:8:2: cannot assign to MaxRetries (neither addressable nor a map index expression)
定数にできるもの
値の種類は3つだけです。真偽値、数値(整数、浮動小数点数、複素数、ルーン)、文字列 です。値はコンパイラが計算できるものでなければならず、リテラル、他の定数、それらの算術演算、そして定数文字列の len のようないくつかの組み込み関数がそれにあたります。
コードを実行したりメモリを確保したりする必要があるものは使えません。
const Colors = []string{"red", "green"} // slice
const Started = time.Now() // function call
./main.go:9:16: []string{…} (value of type []string) is not constant
./main.go:10:13: time.Now() (value of struct type time.Time) is not constant
Goに定数のスライスやマップがない理由
スライス、マップ、そしてそれらを含む構造体は実行時に存在するメモリへの参照なので、コンパイラはそれらを固定値としてプログラムに埋め込めません。また、Goには変数に付ける readonly や final のような修飾子もありません。よく使われる回避策は次のとおりです。
コピーは呼び出しのたびにメモリ確保のコストがかかりますが、設定程度の大きさのデータなら問題ありません。キーで値を引く用途なら、関数の中の switch も定数に近い選択肢です:func statusText(code int) string { switch code { ... } }。
型なし定数
型を付けずに宣言した定数は 型なし(untyped)です。種類(整数、浮動小数点数、ルーン、文字列、真偽値)はありますが、使われるまで具体的なGoの型を持たず、文脈に合わせて変わります。
Ratio は変換なしで int、float64、uint8、float32 として使えます。time.Sleep(2 * time.Second) がコンパイルできるのもこのためで、2 は型なしなので time.Duration になります。
x := Ratio のように文脈が型を要求しない場合、定数は デフォルトの型 になります。
| 型なし定数の種類 | デフォルトの型 |
|---|---|
整数(42) | int |
浮動小数点数(4.2) | float64 |
ルーン('a') | rune(int32) |
複素数(2i) | complex128 |
| 文字列 | string |
| 真偽値 | bool |
正確な算術
型なしの数値定数は正確です。コンパイラは整数を少なくとも256ビットで表現するので、途中の値はGoのどの型よりもはるかに大きくなっても構いません。
Huge そのものは整数として表示できません。fmt.Println に渡すと int に変換され、コンパイラがそれを検出するからです(float64(Huge) なら動きます。浮動小数点数ならこの大きさを表せます)。
cannot use Huge (untyped int constant 1267650600228229401496703205376) as int value in argument to fmt.Println (overflows)
同じチェックが、var b byte = 300 のような小さな間違いもコンパイル時に止めます。
cannot use 300 (untyped int constant) as byte value in variable declaration (overflows)
型付き定数
定数に型を付けると、どこでもその型の値として振る舞い、型を混ぜることを拒むGoのルールもそのまま当てはまります。
const Limit int = 10
var f float64 = 2
fmt.Println(Limit * f)
invalid operation: Limit * f (mismatched types int and float64)
const Limit = 10(型なし)なら、Limit * f はコンパイルでき、20 になります。ですから、型に意味がない限り定数は型なしにしておきましょう。型に意味があるのは、自分で定義した名前付きの型の定数を宣言するときで、Goはこれで列挙型を作ります。
type Weekday int
const (
Sunday Weekday = iota
Monday
Tuesday
)
ここでは型こそが要点です。Weekday を受け取る関数は、何を求めているかを型で示せます。iota とそれを使った列挙型のパターンは、列挙型とiotaのページで扱っています。
constとvarの違い
const | var | |
|---|---|---|
| 変更できるか | できない | できる |
| 値が決まる時点 | コンパイル時 | 実行時 |
| 使える型 | 真偽値、数値、文字列 | 何でも |
| 実行時にメモリを使うか | 使わない | 使う |
アドレスを取れるか(&x) | 取れない | 取れる |
| 未使用だとエラーになるか | ならない | ローカル変数ならなる |
上限、サイズ、名前、プロトコルのコード、書式文字列など、本当に固定されているものには const を使いましょう。コンパイル時のオーバーフローチェックと正確な算術が無料で手に入ります。
落とし穴
型付き定数は文脈に合わせて変わらない。 const Timeout int = 5 は、変換しないと time.Duration が必要な場所に渡せません。型なしの const Timeout = 5 なら掛け算できます:Timeout * time.Second。
整数の定数では整数除算になる。 const Half = 1 / 2 は、どちらのオペランドも型なしの整数なので 0 です。0.5 がほしいなら 1.0 / 2 と書きます。
アドレスがない。 &MaxRetries はコンパイルできません。APIが *int を求めるなら、先に定数を変数にコピーします。
よくある質問
Goで定数はどう宣言しますか?
const を使います:const MaxRetries = 3、型を明示するなら const Timeout time.Duration = 5 * time.Second。複数ならブロックにまとめます:const ( A = 1; B = 2 )。値はコンパイル時に計算できるものでなければなりません。
Goでconstのスライス、マップ、配列は作れますか?
作れません。定数にできるのは真偽値、数値(ルーンを含む)、文字列だけです。const Colors = []string{"red"} は is not constant で失敗します。パッケージレベルの var を使い、呼び出し側に変更させたくない場合は、新しいコピーを返す関数を公開します。
Goの型なし定数(untyped constant)とは何ですか?
const Pi = 3.14159 のように型を付けずに宣言した定数です。使われるまで型が決まらないので、同じ定数が float32 としても float64 としても、値が許せば int としても使えます。型なし定数は正確でもあります。コンパイラは少なくとも256ビットの精度を保つので、結果が収まる式でだけ使うなら const Big = 1 << 100 も正しいコードです。
Goのconstとvarの違いは何ですか?
const はコンパイル時に決まり、決して変わりません。実行時にメモリを使わず、保持できるのは真偽値、数値、文字列だけです。var は再代入できるメモリ上の場所で、どんな型でも、time.Now() のように実行時に計算される値でも保持できます。