1月15日開催!現年収非公開で企業からスカウトをもらってみませんか?PR

転職ドラフトでリアルな市場価値を測る。レジュメをもとに、企業から年収とミッションが提示されます。

6
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

N高グループ・N中等部Advent Calendar 2024

Day 18

Go の stringer (ツール)でコードの変更を検知してコンパイルエラーを発生させることができる理由

Posted at

この記事は N高グループ・N中等部 Advent Calendar 2024 18日目の記事です。N高生です。

Go には、整数型に fmt.Stringer インターフェースのコードを生成してくれる stringer というツールがあります。自分で定義した型と定数を使って、列挙型のような使い方ができます。go generate した後に、定義側を変更して生成コード側との間で不整合を起こすと、コンパイルエラーが発生します。

使い方

  1. インストール

    go install golang.org/x/tools/cmd/stringer@latest
    
  2. 型と定数を定義

    -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               // 亥
    )
    
  3. コマンド実行

    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] (この場合 Ne1) 、 1-1 = 0 なので正常にアクセスできる。
同じように、 Ushi2 なので 2-2 = 0Tora3-3 = 0 でそれぞれ正常にアクセスすることができる。

定数宣言リストの間に新しい定数を追加すると、それより後ろの定数の値がズレて、

現在の定数の値 - コード生成時の定数の値 = 0

の式が成り立たなくなってしまい、 index が範囲外で InValidIndex エラー になる。

リストから定数を削除した場合は、削除された定数は未宣言で UndeclaredName エラー、それより後ろはすべて index が負となってしまいこれも InValidIndex エラー になる。

再度 go generate で生成するとコンパイルが通るようになる。

参考

6
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?