この記事は N高グループ・N中等部 Advent Calendar 2024 18日目の記事です。N高生です。
Go には、整数型に fmt.Stringer
インターフェースのコードを生成してくれる stringer というツールがあります。自分で定義した型と定数を使って、列挙型のような使い方ができます。go generate
した後に、定義側を変更して生成コード側との間で不整合を起こすと、コンパイルエラーが発生します。
使い方
-
インストール
go install golang.org/x/tools/cmd/stringer@latest
-
型と定数を定義
-linecomment
フラグで行コメントをString()
の返り値として使うようになる。package main import "fmt" //go:generate stringer -linecomment -type=Junishi type Junishi int const ( _ Junishi = iota Ne // 子 Ushi // 丑 Tora // 寅 U // 卯 Tatsu // 辰 Mi // 巳 Uma // 午 Hitsuji // 未 Saru // 申 Tori // 酉 Inu // 戌 I // 亥 )
-
コマンド実行
go generate
xxx_string ファイルが生成される。
これで、 String()
メソッドで文字列を返せるようになった。
func junishi(year int) string {
// 西暦から 3 引いて 12 で割った余り
return Junishi((year - 3) % 12).String()
}
func main() {
junishi(2024) // 辰
junishi(2025) // 巳
}
↑ 年から十二支の文字列を返す関数
新しい定数を追加してみる
const (
_ Junishi = iota
Ne // 子
Ushi // 丑
Tora // 寅
U // 卯
Tatsu // 辰
_
Mi // 巳
Uma // 午
Hitsuji // 未
Saru // 申
Tori // 酉
Inu // 戌
I // 亥
)
辰年と巳年の間に新しい定数を追加。これにより巳年以降の値が1ずつズレて、コンパイルが通らなくなる。
$ go build .
# junishi
./junishi_string.go:16:8: invalid argument: index 1 out of bounds [0:1]
./junishi_string.go:17:8: invalid argument: index 1 out of bounds [0:1]
./junishi_string.go:18:8: invalid argument: index 1 out of bounds [0:1]
./junishi_string.go:19:8: invalid argument: index 1 out of bounds [0:1]
./junishi_string.go:20:8: invalid argument: index 1 out of bounds [0:1]
./junishi_string.go:21:8: invalid argument: index 1 out of bounds [0:1]
./junishi_string.go:22:8: invalid argument: index 1 out of bounds [0:1]
↑ ❗️ コンパイル通らない
理由
junishi_string.go を確認すると、生成されたコードに Blank identfier な関数が定義されている。
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[Ne-1]
_ = x[Ushi-2]
_ = x[Tora-3]
_ = x[U-4]
_ = x[Tatsu-5]
_ = x[Mi-6]
_ = x[Uma-7]
_ = x[Hitsuji-8]
_ = x[Saru-9]
_ = x[Tori-10]
_ = x[Inu-11]
_ = x[I-12]
}
↑ 生成されたコードの一部
関数の中では、長さ 1 の空 struct の配列が定義されていて、 現在の定数の値とコード生成時の定数の値を基に index を計算してアクセスし、結果を _
に代入している。
_ = x[Ne-1]
(この場合 Ne
は 1) 、 1-1
= 0 なので正常にアクセスできる。
同じように、 Ushi
は 2 なので 2-2
= 0 、 Tora
は 3-3 = 0 でそれぞれ正常にアクセスすることができる。
定数宣言リストの間に新しい定数を追加すると、それより後ろの定数の値がズレて、
現在の定数の値 - コード生成時の定数の値 = 0
の式が成り立たなくなってしまい、 index が範囲外で InValidIndex エラー になる。
リストから定数を削除した場合は、削除された定数は未宣言で UndeclaredName エラー、それより後ろはすべて index が負となってしまいこれも InValidIndex エラー になる。
再度 go generate
で生成するとコンパイルが通るようになる。
参考