const キーワードは、タイムアウト、ヘッダー名、サイズの上限など、コンパイラがビルド時に知っている値に名前を付けます。Go の定数に入れられるのは、真偽値、数値、文字列、rune だけです。プログラムの実行中に値が変わることはありません。ほとんどの定数は型なしです。つまり、使われるまで型が決まっていません。そのため const maxBodyBytes = 1 << 20 は、変換なしで int としても int64 としても float64 としても使えます。const ブロックの中では、事前宣言された識別子 iota が 0 から数え上げます。Go ではこれを使って enum を書きます(Go spec)。
要約
const name = valueは定数を 1 つ宣言します。const ( ... )ブロックは複数の定数を宣言します。ほとんどの Go のコードでは、関連する定数をこの形でまとめます。- 定数にできるのは、真偽値、数値、文字列、rune です。slice、map、struct や、
time.Now()のような関数呼び出しの結果は定数にできません。 5や"GET"のような型なし定数は、文脈が必要とする型になります。5 * time.Secondがコンパイルできるのはこのためです。一方、int型の変数を使ったtimeout * time.Secondはコンパイルできません。- 型なし定数は正確です。コンパイラは任意精度で算術演算を行い、最終的に入る型に値が収まらないときだけエラーを出します。
iotaはconstブロックごとに 0 から始まり、1 行ごとに 1 ずつ増えます。_で値をスキップでき、iota + 1で 1 から始められます。1 << iotaでビットフラグを作れます。- Go には
enumキーワードがありません。enum は、名前付きの型、iotaを使ったconstブロック、そして通常はString()メソッドで構成します。このメソッドはstringerツールで生成できます。 - 定数のアドレスを取ったり、定数に代入したりすることはできません。実行時に計算する値や、map のようなルックアップテーブルは
varで宣言します。
Go で定数を宣言するには?
const、名前、値の順に書きます。関連する定数が複数あるときは、括弧でまとめたブロックを使います。
example.gogopackage main import ( "fmt" "net/http" "time" ) const maxBodyBytes = 1 << 20 // 1 MiB const ( readTimeout = 5 * time.Second writeTimeout = 10 * time.Second idleTimeout = 2 * time.Minute ) const ( headerRequestID = "X-Request-ID" headerRetryAfter = "Retry-After" ) func main() { srv := &http.Server{ Addr: ":8080", ReadTimeout: readTimeout, WriteTimeout: writeTimeout, IdleTimeout: idleTimeout, } fmt.Println(srv.ReadTimeout, srv.WriteTimeout, srv.IdleTimeout) }
このプログラムは 5s 10s 2m0s と出力します。このような単純な値の場合、グループ化は読み手のためのものです。ブロック内の名前はそれぞれ独立した定数です。ブロックにまとめておくと、サーバーのタイムアウトが 1 か所に並び、レビューする人が見比べやすくなります。
定数のスコープと可視性のルールは変数と同じです。パッケージレベルで宣言した定数はパッケージ内のすべてのファイルから見えます。http.StatusNotFound や time.RFC3339 のように名前が大文字で始まれば、エクスポートされます。エクスポートされた名前がパッケージをまたいでどう働くかは、Go の package キーワードで解説しています。関数の中で宣言した定数は、その関数の中にしか存在しません。Go のスタイルでは、定数もほかの名前と同じく MixedCaps で書きます。したがって Go らしい書き方は maxBodyBytes や MaxBodyBytes で、MAX_BODY_BYTES ではありません。
const ブロックには、var ブロックにはないルールが 1 つあります。名前だけがあり、型も値もない行は、直前の行の型と式を繰り返します。これだけでは、複数の名前に同じ値を与えるだけで、そうしたい場面はめったにありません。iota と組み合わせれば、1 つの式で定数のリスト全体に番号を振れます。
Go で定数にできる値は?
真偽値、数値、文字列、rune だけです。数値には整数、浮動小数点数、複素数があります。rune は Unicode のコードポイントを表す整数です。それ以外はコンパイルエラーになります。実際には変わらない値でも、実行時に計算する必要があるものは定数にできません。
example.gogopackage main import "time" const startedAt = time.Now()
example.texttext./main.go:5:19: time.Now() (value of struct type time.Time) is not constant
複合的な値も同じように拒否されます。const allowedMethods = []string{"GET", "POST"} は []string{…} (value of type []string) is not constant で失敗します。slice、map、struct、ポインター、channel、関数は、要素がすべてリテラルであっても定数にできません。
数値、文字列、真偽値をもとにした名前付きの型は使えます。time.Duration は int64 として定義されているので、const pollInterval = 500 * time.Millisecond は値が 500ms の time.Duration 型の定数です。type LogLevel int のような自分で定義した型も同じで、enum はこれをもとに作ります。
組み込み関数の中にも、引数によっては定数を返すものがあります。配列の長さは型の一部なので、配列に対する len は定数です。文字列定数に対する len("Retry-After") も定数です。unsafe.Sizeof、unsafe.Alignof、unsafe.Offsetof は、引数の型のサイズが固定であれば定数を返すので、const headerSize = unsafe.Sizeof(int64(0)) はコンパイルでき、値は 8 になります。slice の長さは実行時にしかわからないので、slice に対する len は定数ではありません。
Go の型付き定数と型なし定数の違いは?
型付き定数は、宣言の中で型が指定されています。型なし定数には型がなく、周囲のコードが必要とする型になります。
example.gogoconst maxRetries int32 = 3 // typed: always an int32 const maxBodyBytes = 1 << 20 // untyped: becomes whatever type it is used as
型付き定数は通常の代入のルールに従うので、変換なしで int に入れることはできません。
example.gogovar attempts int = maxRetries
example.texttext./main.go:6:21: cannot use maxRetries (constant 3 of type int32) as int value in variable declaration
型なし定数なら、var limit int64 = maxBodyBytes にも var limit int = maxBodyBytes にも float64(maxBodyBytes) にも同じように使えます。標準ライブラリはこの柔軟さを利用しており、math.MaxInt64 や math.Pi のように、数値定数のほとんどを型なしにしています。
型なし定数にもデフォルトの型があります。x := 1.5 のように型を決めるものがほかにないときや、定数を fmt.Println に渡すときに使われます。
| 型なし定数 | デフォルトの型 |
|---|---|
true, false | bool |
42, 1 << 20 | int |
1.5, 1e9 | float64 |
'a' | rune(int32 のエイリアス) |
2i | complex128 |
"GET" | string |
5 * time.Second はコンパイルできるのに timeout * time.Second が失敗するのはなぜか?
5 は型なし定数で、timeout は変数だからです。Go は数値型の間で自動的に変換を行いません。しかし型なし定数は、値が収まる数値型であればどの型にもなれます。5 * time.Second では 5 が time.Duration になり、結果は 5 秒を表す time.Duration です。
変数にはすでに型があるので、数値が設定から来る場合は同じ掛け算が失敗します。
example.gogopackage main import ( "net/http" "os" "strconv" "time" ) func main() { timeout, _ := strconv.Atoi(os.Getenv("TIMEOUT_SECONDS")) client := &http.Client{Timeout: timeout * time.Second} _ = client }
example.texttext./main.go:12:34: invalid operation: timeout * time.Second (mismatched types int and "time".Duration)
timeout は int で、time.Second は time.Duration です。明示的に time.Duration(timeout) * time.Second と変換すれば直ります。同じ値の型なし定数なら変換は不要です。const retryDelay = 3 と宣言して retryDelay * time.Second と書けば、3s になります。
型なし定数の精度はどのくらいか?
型なしの数値定数は正確です。言語仕様は、整数定数を少なくとも 256 ビットで表現し、浮動小数点定数を少なくとも 256 ビットの仮数で正確に保つことをコンパイラに求めています。標準の Go コンパイラは最大 512 ビットまで扱えます。math.Pi は float64 で表せるよりも多くの桁で書かれており、float64 や float32 の変数に入るときに初めて丸められます。
この精度のおかげで、最終的な結果が収まる限り、途中の値はどの Go の型にも収まらないほど大きくても構いません。const maxOffset = 1 << 100 はコンパイルでき、maxOffset >> 98 は定数 4 になります。エラーになるのは、値を具体的な型にしなければならないときだけです。
example.gogopackage main import "fmt" const maxOffset = 1 << 100 func main() { fmt.Println(maxOffset) }
example.texttext./main.go:8:14: cannot use maxOffset (untyped int constant 1267650600228229401496703205376) as int value in argument to fmt.Println (overflows)
コンパイラはすべての定数の値を知っているので、こうした範囲チェックは、実行時に黙ってラップアラウンドするのではなく、ビルド時に行われます。ほかにも次のようなエラーがあります。
| コード | コンパイルエラー |
|---|---|
var uploadLimit int32 = 1 << 40 | cannot use 1 << 40 (untyped int constant 1099511627776) as int32 value in variable declaration (overflows) |
const maxConns uint8 = 300 | cannot use 300 (untyped int constant) as uint8 value in constant declaration (overflows) |
var workers int = 2.5 | cannot use 2.5 (untyped float constant) as int value in variable declaration (truncated) |
チェックが見るのは値だけです。var workers int = 2.0 はコンパイルできます。2.0 は型なしの浮動小数点定数ですが、値が整数だからです。
Go の iota とは?
iota は事前宣言された識別子で、const 宣言の中でだけ意味を持ちます。const ブロックの最初の定数仕様(spec)では 0 で、その後の spec ごとに 1 ずつ増えます。spec とは名前を宣言する 1 行のことなので、空行やコメントは数えません。直前の式が暗黙に繰り返される仕組みと組み合わせれば、型を 1 つと iota を 1 回書くだけで、リスト全体に番号を振れます。
example.gogotype LogLevel int const ( LevelDebug LogLevel = iota // 0 LevelInfo // 1 LevelWarn // 2 LevelError // 3 )
LevelInfo、LevelWarn、LevelError には独自の型も値もないので、それぞれが LogLevel = iota を繰り返し、iota の次の値を受け取ります。リスト内の定数はすべて LogLevel 型です。
iota の数え方は次のとおりです。
iotaはconstブロックが始まるたびに 0 に戻ります。別々の 2 つのブロックは、どちらも 0 から始まります。- 数えるのは名前ではなく spec です。同じ行で宣言した 2 つの定数は、同じ
iotaの値になります。 _を使った行も数に入るので、ブランク識別子で値をスキップできます。iotaは型なしの整数定数なので、iota + 1や1 << iotaのように、どんな定数式でも使えます。
iota を 1 から始めたり、値をスキップしたりするには?
1 を足すか、最初の値を _ で捨てます。1 から始めると、ゼロ値を「未設定」の意味に使えます。
example.gogotype Priority int const ( PriorityLow Priority = iota + 1 // 1 PriorityNormal // 2 PriorityHigh // 3 )
_ でのスキップは、最初の値に意味がない場合に向いています。次のサイズの単位では、1 << 0 だと 1 バイトになってしまうので、iota == 0 をスキップしています。
example.gogotype ByteSize int64 const ( _ = iota // skip 0 KB ByteSize = 1 << (10 * iota) MB GB )
KB は 1 << 10 で 1024、MB は 1 << 20、GB は 1 << 30 です。各行が、より大きな iota で ByteSize = 1 << (10 * iota) を繰り返しています。
iota でビットフラグを定義するには?
1 を iota だけ左シフトします。すると各定数がそれぞれ別のビットを持ち、| で組み合わせられます。
example.gogopackage main import "fmt" type Permission uint8 const ( PermRead Permission = 1 << iota // 1 PermWrite // 2 PermDelete // 4 ) func main() { perm := PermRead | PermWrite fmt.Println(perm&PermWrite != 0) // true fmt.Println(perm&PermDelete != 0) // false }
標準ライブラリも同じパターンを使っています。log.Ldate、log.Ltime、log.Lshortfile は 1 << iota のフラグで、log.SetFlags(log.LstdFlags | log.Lshortfile) のように組み合わせて使います。
Go に enum はあるのか?
独立した言語機能としてはありません。enum キーワードは存在しません。Go は、すでにある 3 つの要素で enum を作ります。名前付きの型、iota を使った const ブロック、そして値を数値ではなく名前で出力するための String() メソッドです。
example.gogopackage main import "fmt" type LogLevel int const ( LevelDebug LogLevel = iota LevelInfo LevelWarn LevelError ) func (l LogLevel) String() string { switch l { case LevelDebug: return "debug" case LevelInfo: return "info" case LevelWarn: return "warn" case LevelError: return "error" default: return fmt.Sprintf("LogLevel(%d)", int(l)) } } func main() { fmt.Println(LevelWarn, LogLevel(9)) fmt.Printf("level=%v\n", LevelError) }
このプログラムは warn LogLevel(9) と出力し、続けて level=error と出力します。fmt は、Println、%v、%s で値を出力するときに String() を呼び出します。default の分岐は、LogLevel(9) のようにリストにない値を読める形に変えます。
stringer で String メソッドを生成するには?
この switch を手で書くのは面倒です。また、誰かが定数を追加してメソッドの更新を忘れると、内容が古くなります。Go チームの stringer ツールを使えば、これを生成できます。型の横に go:generate ディレクティブを書き、go generate を実行します。
example.gogopackage main import "fmt" //go:generate go run golang.org/x/tools/cmd/stringer@latest -type=OrderStatus -trimprefix=OrderStatus type OrderStatus int const ( OrderStatusUnknown OrderStatus = iota OrderStatusPending OrderStatusPaid OrderStatusShipped OrderStatusRefunded ) func main() { fmt.Println(OrderStatusPaid, OrderStatus(42)) }
go generate は String() メソッドを含む orderstatus_string.go を書き出し、プログラムは Paid OrderStatus(42) と出力します。-trimprefix は、名前に共通する接頭辞を取り除きます。生成されたファイルには、コンパイル時の小さなチェックも含まれています。既存の定数の値が変わったのにファイルが再生成されていないと、invalid argument: index 4 out of bounds [0:1] のようなエラーでビルドが失敗します。ただし、リストの末尾に追加した定数は検出されず、もう一度 go generate を実行するまで OrderStatus(5) と出力されます。そのため多くのチームは CI で go generate を実行し、生成されたファイルに差分が出たらビルドを失敗させています。
enum のゼロ値を Unknown にすべきなのはなぜか?
Go の変数はすべてゼロ値から始まり、int をもとにした enum ではそれが 0 だからです。status フィールドのない JSON からデコードした Order 構造体や、var o Order で宣言した Order は、status が 0 になります。0 が OrderStatusPending だと、値がないだけなのに、本当に支払い待ちの注文のように見えてしまいます。最初に明示的な OrderStatusUnknown を置くか、iota + 1 から始めれば、ゼロ値は「未設定」を意味するようになり、コードでそれを拒否できます。
switch が enum のすべての値を網羅しているか Go はチェックするのか?
チェックしません。enum の型も普通の整数型なので、どの値が有効なのかをコンパイラは知りません。9 は int に収まる型なし定数なので、var level LogLevel = 9 はコンパイルできます。一部の値しか扱わない switch も、警告なしでコンパイルされます。
このチェックが必要なチームは、golangci-lint に含まれている exhaustive リンターを追加します。このリンターは、enum 型に対する switch がその型の定数を 1 つでも扱っていないと報告します。リストにない値は、上の String() メソッドのような default の分岐で、実行時に処理します。
Go で const と var はどう使い分けるのか?
コードを書く時点でわかっていて、決して変わらない値には const を使います。タイムアウト、上限値、ヘッダー名、enum の値などです。実行時に計算する値、設定から読み込む値、複合型で作る値には var を使います。
定数は変数に格納されるわけではないので、定数のアドレスは取れません。
example.gogop := &maxBodyBytes
example.texttext./main.go:6:8: invalid operation: cannot take address of maxBodyBytes (untyped int constant 1048576)
これは、省略可能なフィールドをポインターで受け取る API で問題になります。Go 1.26 からは new が式を受け取れるので、new(maxBodyBytes) は値のコピーを指す *int を返します。それより古いバージョンでは、先に定数を変数にコピーし、そのアドレスを取ります。
定数に代入することもできません。maxBodyBytes = 2 << 20 は cannot assign to maxBodyBytes (neither addressable nor a map index expression) で失敗します。
使われていない定数はコンパイルできます。使われていないローカル変数は declared and not used: retries というエラーになりますが、使われていない定数は、関数の中にあっても受け入れられます。ただし staticcheck のようなリンターは、パッケージレベルにある未使用のエクスポートされていない定数を報告します。
ルックアップテーブルは変数です。ステータスからラベルへの map は定数にできないので、パッケージレベルで var を使って宣言します。ほかのコードに変更させたくない場合は、関数から返すようにします。
example.gogovar orderStatusLabels = map[OrderStatus]string{ OrderStatusPending: "Awaiting payment", OrderStatusPaid: "Paid", OrderStatusShipped: "On the way", OrderStatusRefunded: "Refunded", }
パッケージレベルの var はどんな型でも保持できる代わりに、パッケージ内のどのコードからでも変更できてしまいます。値が固定されていて単純なら、定数を使えばコンパイル時のチェックが得られ、実行時に変更される心配もありません。変数、ゼロ値、:= については Go の var キーワードで詳しく解説しています。
LevelUpGo で学ぶ
LevelUpGo では、ブラウザ上で本物の Go コードを実行する演習を通じて Go を学べます。Go Basics では、変数や型と一緒に const を紹介します。Go Language Deep Dives には iota と const のパターンを扱うレッスンがあり、enum、ビットフラグ、バイトサイズの単位を作ります。Training Ground には、コースの外で練習できる短い単独の演習があります。残り 24 個の予約語については、Go のキーワード一覧:全 25 個の予約語を解説をご覧ください。
よくある質問
Go で const はキーワードですか?
はい。const は Go の 25 個の予約語の 1 つなので、変数名、関数名、型名には使えません。定数宣言を始めるキーワードで、1 つだけの const name = value と、まとめて書く const ( ... ) ブロックのどちらにも使います。
Go で slice や map を定数にできますか?
できません。定数にできるのは、真偽値、数値、文字列、rune と、それらをもとにした名前付きの型だけです。const allowedMethods = []string{"GET", "POST"} は is not constant で失敗します。slice や map にはパッケージレベルの var を使います。共有している値を呼び出し側に変更させてはいけない場合は、新しいコピーを返す関数を使います。
Go の iota とは何ですか?
iota は、const ブロックの行に番号を振る事前宣言された識別子です。最初の行では 0 で、その後の行ごとに 1 ずつ増え、新しいブロックごとに 0 に戻ります。名前付きの型と暗黙の繰り返しを組み合わせると、リスト内の各定数にそれぞれ別の値を与えられます。Go ではこの方法で enum やビットフラグを書きます。
Go の型付き定数と型なし定数の違いは何ですか?
const maxRetries int32 = 3 のような型付き定数は、型が固定されていて、通常の代入のルールに従います。const maxRetries = 3 のような型なし定数は、使われるまで型を持たないので、int、int64、float64、time.Duration のどれにもなれます。また型なし定数は、変換されるまで正確な任意精度の値を保ちます。
Go に enum はありますか?
Go に enum キーワードはありませんが、実質的には enum があります。type OrderStatus int のような名前付きの型を宣言し、iota を使った const ブロックにその値を並べ、String() メソッドを追加します。このメソッドは stringer で生成することが多いです。ただし、この型に入る値が、並べた定数だけに制限されるわけではありません。そのため入力は検証し、すべての switch ですべての値を扱う必要があるなら exhaustive リンターを使いましょう。
出典
- The Go Programming Language Specification, Constants: https://go.dev/ref/spec#Constants
- The Go Programming Language Specification, Constant declarations: https://go.dev/ref/spec#Constant_declarations
- The Go Programming Language Specification, Iota: https://go.dev/ref/spec#Iota
- The Go Programming Language Specification, Representability: https://go.dev/ref/spec#Representability
- The Go Blog, Constants (Rob Pike): https://go.dev/blog/constants
- Effective Go, Constants: https://go.dev/doc/effective_go#constants
- stringer: https://pkg.go.dev/golang.org/x/tools/cmd/stringer
- Go Code Review Comments, Mixed caps: https://go.dev/wiki/CodeReviewComments#mixed-caps
- exhaustive リンター: https://github.com/nishanths/exhaustive
