KDOC 54: Goのiotaの仕組み

Go言語にはiotaというキーワードがある。enum的なものを実現したいときによく使う機能で、整数を自動的に割り振ってくれる。

例えば、↓のように、Languageというグループとして定数をまとめて定義できる。実際に割り当てられている値が何か意識しなくてよい。

type Language int
const (
        Japanese Language = iota
        English
        Chinese
)

これをどうやっているのか、iotaの仕組みについて調べる。まず生成されるアセンブリコードを比較してみる↓。

package main
import "fmt"
func main() {
        const a = iota + 999
        fmt.Print(a)
}

main.main STEXT size=114 args=0x0 locals=0x50 funcid=0x0 align=0x0 0x0000 00000 (/tmp/babel-ZDNznW/hxQ4Zh.go:3) TEXT main.main(SB), ABIInternal, $80-0 0x0000 00000 (/tmp/babel-ZDNznW/hxQ4Zh.go:3) CMPQ SP, 16(R14) 0x0004 00004 (/tmp/babel-ZDNznW/hxQ4Zh.go:3) PCDATA $0, $-2 0x0004 00004 (/tmp/babel-ZDNznW/hxQ4Zh.go:3) JLS 107 0x0006 00006 (/tmp/babel-ZDNznW/hxQ4Zh.go:3) PCDATA $0, $-1 0x0006 00006 (/tmp/babel-ZDNznW/hxQ4Zh.go:3) PUSHQ BP 0x0007 00007 (/tmp/babel-ZDNznW/hxQ4Zh.go:3) MOVQ SP, BP 0x000a 00010 (/tmp/babel-ZDNznW/hxQ4Zh.go:3) SUBQ $72, SP 0x000e 00014 (/tmp/babel-ZDNznW/hxQ4Zh.go:3) FUNCDATA $0, gclocals·g2BeySu+wFnoycgXfElmcg==(SB) 0x000e 00014 (/tmp/babel-ZDNznW/hxQ4Zh.go:3) FUNCDATA $1, gclocals·/ydTHfVJHvKeH/UP4dRKSQ==(SB) 0x000e 00014 (/tmp/babel-ZDNznW/hxQ4Zh.go:3) FUNCDATA $2, main.main.stkobj(SB) 0x000e 00014 (/tmp/babel-ZDNznW/hxQ4Zh.go:5) MOVUPS X15, main..autotmp_0+32(SP) 0x0014 00020 (/tmp/babel-ZDNznW/hxQ4Zh.go:5) LEAQ main..autotmp_0+32(SP), AX 0x0019 00025 (/tmp/babel-ZDNznW/hxQ4Zh.go:5) MOVQ AX, main..autotmp_2+24(SP) 0x001e 00030 (/tmp/babel-ZDNznW/hxQ4Zh.go:5) TESTB AL, (AX) 0x0020 00032 (/tmp/babel-ZDNznW/hxQ4Zh.go:5) LEAQ type:int(SB), DX 0x0027 00039 (/tmp/babel-ZDNznW/hxQ4Zh.go:5) MOVQ DX, main..autotmp_0+32(SP) 0x002c 00044 (/tmp/babel-ZDNznW/hxQ4Zh.go:5) LEAQ main..stmp_0(SB), DX 0x0033 00051 (/tmp/babel-ZDNznW/hxQ4Zh.go:5) MOVQ DX, main..autotmp_0+40(SP) 0x0038 00056 (/tmp/babel-ZDNznW/hxQ4Zh.go:5) TESTB AL, (AX) 0x003a 00058 (/tmp/babel-ZDNznW/hxQ4Zh.go:5) JMP 60 0x003c 00060 (/tmp/babel-ZDNznW/hxQ4Zh.go:5) MOVQ AX, main..autotmp_1+48(SP) 0x0041 00065 (/tmp/babel-ZDNznW/hxQ4Zh.go:5) MOVQ $1, main..autotmp_1+56(SP) 0x004a 00074 (/tmp/babel-ZDNznW/hxQ4Zh.go:5) MOVQ $1, main..autotmp_1+64(SP) 0x0053 00083 (/tmp/babel-ZDNznW/hxQ4Zh.go:5) MOVL $1, BX 0x0058 00088 (/tmp/babel-ZDNznW/hxQ4Zh.go:5) MOVQ BX, CX 0x005b 00091 (/tmp/babel-ZDNznW/hxQ4Zh.go:5) PCDATA $1, $0 0x005b 00091 (/tmp/babel-ZDNznW/hxQ4Zh.go:5) NOP 0x0060 00096 (/tmp/babel-ZDNznW/hxQ4Zh.go:5) CALL fmt.Print(SB) 0x0065 00101 (/tmp/babel-ZDNznW/hxQ4Zh.go:6) ADDQ $72, SP 0x0069 00105 (/tmp/babel-ZDNznW/hxQ4Zh.go:6) POPQ BP 0x006a 00106 (/tmp/babel-ZDNznW/hxQ4Zh.go:6) RET 0x006b 00107 (/tmp/babel-ZDNznW/hxQ4Zh.go:6) NOP 0x006b 00107 (/tmp/babel-ZDNznW/hxQ4Zh.go:3) PCDATA $1, $-1 0x006b 00107 (/tmp/babel-ZDNznW/hxQ4Zh.go:3) PCDATA $0, $-2 0x006b 00107 (/tmp/babel-ZDNznW/hxQ4Zh.go:3) CALL runtime.morestack_noctxt(SB) 0x0070 00112 (/tmp/babel-ZDNznW/hxQ4Zh.go:3) PCDATA $0, \(-1 0x0070 00112 (/tmp/babel-ZDNznW/hxQ4Zh.go:3) JMP 0 0x0000 49 3b 66 10 76 65 55 48 89 e5 48 83 ec 48 44 0f I;f.veUH..H..HD. 0x0010 11 7c 24 20 48 8d 44 24 20 48 89 44 24 18 84 00 .|\) H.D$ H.D$… 0x0020 48 8d 15 00 00 00 00 48 89 54 24 20 48 8d 15 00 H……H.T$ H… 0x0030 00 00 00 48 89 54 24 28 84 00 eb 00 48 89 44 24 …H.T\((....H.D\) 0x0040 30 48 c7 44 24 38 01 00 00 00 48 c7 44 24 40 01 0H.D\(8....H.D\)@. 0x0050 00 00 00 bb 01 00 00 00 48 89 d9 0f 1f 44 00 00 ……..H….D.. 0x0060 e8 00 00 00 00 48 83 c4 48 5d c3 e8 00 00 00 00 …..H..H]…… 0x0070 eb 8e .. rel 2+0 t=23 type:int+0 rel 35+4 t=14 type:int+0 rel 47+4 t=14 main..stmp_0+0 rel 97+4 t=7 fmt.Print+0 rel 108+4 t=7 runtime.morestack_noctxt+0 go:cuinfo.producer.main SDWARFCUINFO dupok size=0 0x0000 2d 4e 20 2d 6c 20 72 65 67 61 62 69 -N -l regabi go:cuinfo.packagename.main SDWARFCUINFO dupok size=0 0x0000 6d 61 69 6e main main..inittask SNOPTRDATA size=8 0x0000 00 00 00 00 00 00 00 00 …….. rel 0+0 t=81 fmt..inittask+0 main..stmp_0 SRODATA static size=8 0x0000 e7 03 00 00 00 00 00 00 …….. runtime.nilinterequal·f SRODATA dupok size=8 0x0000 00 00 00 00 00 00 00 00 …….. rel 0+8 t=1 runtime.nilinterequal+0 runtime.gcbits.0200000000000000 SRODATA dupok size=8 0x0000 02 00 00 00 00 00 00 00 …….. type:.namedata.*[1]interface {}- SRODATA dupok size=18 0x0000 00 10 2a 5b 31 5d 69 6e 74 65 72 66 61 63 65 20 ..*[1]interface 0x0010 7b 7d {} type:[1]interface {} SRODATA dupok size=72 0x0000 10 00 00 00 00 00 00 00 10 00 00 00 00 00 00 00 ……………. 0x0010 6e 20 6a 3d 02 08 08 11 00 00 00 00 00 00 00 00 n j=………… 0x0020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ……………. 0x0030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ……………. 0x0040 01 00 00 00 00 00 00 00 …….. rel 24+8 t=1 runtime.nilinterequal·f+0 rel 32+8 t=1 runtime.gcbits.0200000000000000+0 rel 40+4 t=5 type:.namedata.*[1]interface {}-+0 rel 44+4 t=-32763 type:*[1]interface {}+0 rel 48+8 t=1 type:interface {}+0 rel 56+8 t=1 type:[]interface {}+0 runtime.memequal64·f SRODATA dupok size=8 0x0000 00 00 00 00 00 00 00 00 …….. rel 0+8 t=1 runtime.memequal64+0 runtime.gcbits.0100000000000000 SRODATA dupok size=8 0x0000 01 00 00 00 00 00 00 00 …….. type:*[1]interface {} SRODATA dupok size=56 0x0000 08 00 00 00 00 00 00 00 08 00 00 00 00 00 00 00 ……………. 0x0010 a8 0e 57 36 08 08 08 36 00 00 00 00 00 00 00 00 ..W6…6…….. 0x0020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ……………. 0x0030 00 00 00 00 00 00 00 00 …….. rel 24+8 t=1 runtime.memequal64·f+0 rel 32+8 t=1 runtime.gcbits.0100000000000000+0 rel 40+4 t=5 type:.namedata.*[1]interface {}-+0 rel 48+8 t=1 type:[1]interface {}+0 type:.importpath.fmt. SRODATA dupok size=5 0x0000 00 03 66 6d 74 ..fmt gclocals·g2BeySu+wFnoycgXfElmcg== SRODATA dupok size=8 0x0000 01 00 00 00 00 00 00 00 …….. gclocals·/ydTHfVJHvKeH/UP4dRKSQ== SRODATA dupok size=9 0x0000 01 00 00 00 06 00 00 00 00 ……… main.main.stkobj SRODATA static size=24 0x0000 01 00 00 00 00 00 00 00 d8 ff ff ff 10 00 00 00 ……………. 0x0010 10 00 00 00 00 00 00 00 …….. rel 20+4 t=5 runtime.gcbits.0200000000000000+0

package main
import "fmt"
func main() {
        fmt.Print(999)
}

main.main STEXT size=114 args=0x0 locals=0x50 funcid=0x0 align=0x0 0x0000 00000 (/tmp/babel-ZDNznW/voZLcz.go:3) TEXT main.main(SB), ABIInternal, $80-0 0x0000 00000 (/tmp/babel-ZDNznW/voZLcz.go:3) CMPQ SP, 16(R14) 0x0004 00004 (/tmp/babel-ZDNznW/voZLcz.go:3) PCDATA $0, $-2 0x0004 00004 (/tmp/babel-ZDNznW/voZLcz.go:3) JLS 107 0x0006 00006 (/tmp/babel-ZDNznW/voZLcz.go:3) PCDATA $0, $-1 0x0006 00006 (/tmp/babel-ZDNznW/voZLcz.go:3) PUSHQ BP 0x0007 00007 (/tmp/babel-ZDNznW/voZLcz.go:3) MOVQ SP, BP 0x000a 00010 (/tmp/babel-ZDNznW/voZLcz.go:3) SUBQ $72, SP 0x000e 00014 (/tmp/babel-ZDNznW/voZLcz.go:3) FUNCDATA $0, gclocals·g2BeySu+wFnoycgXfElmcg==(SB) 0x000e 00014 (/tmp/babel-ZDNznW/voZLcz.go:3) FUNCDATA $1, gclocals·/ydTHfVJHvKeH/UP4dRKSQ==(SB) 0x000e 00014 (/tmp/babel-ZDNznW/voZLcz.go:3) FUNCDATA $2, main.main.stkobj(SB) 0x000e 00014 (/tmp/babel-ZDNznW/voZLcz.go:4) MOVUPS X15, main..autotmp_0+32(SP) 0x0014 00020 (/tmp/babel-ZDNznW/voZLcz.go:4) LEAQ main..autotmp_0+32(SP), AX 0x0019 00025 (/tmp/babel-ZDNznW/voZLcz.go:4) MOVQ AX, main..autotmp_2+24(SP) 0x001e 00030 (/tmp/babel-ZDNznW/voZLcz.go:4) TESTB AL, (AX) 0x0020 00032 (/tmp/babel-ZDNznW/voZLcz.go:4) LEAQ type:int(SB), DX 0x0027 00039 (/tmp/babel-ZDNznW/voZLcz.go:4) MOVQ DX, main..autotmp_0+32(SP) 0x002c 00044 (/tmp/babel-ZDNznW/voZLcz.go:4) LEAQ main..stmp_0(SB), DX 0x0033 00051 (/tmp/babel-ZDNznW/voZLcz.go:4) MOVQ DX, main..autotmp_0+40(SP) 0x0038 00056 (/tmp/babel-ZDNznW/voZLcz.go:4) TESTB AL, (AX) 0x003a 00058 (/tmp/babel-ZDNznW/voZLcz.go:4) JMP 60 0x003c 00060 (/tmp/babel-ZDNznW/voZLcz.go:4) MOVQ AX, main..autotmp_1+48(SP) 0x0041 00065 (/tmp/babel-ZDNznW/voZLcz.go:4) MOVQ $1, main..autotmp_1+56(SP) 0x004a 00074 (/tmp/babel-ZDNznW/voZLcz.go:4) MOVQ $1, main..autotmp_1+64(SP) 0x0053 00083 (/tmp/babel-ZDNznW/voZLcz.go:4) MOVL $1, BX 0x0058 00088 (/tmp/babel-ZDNznW/voZLcz.go:4) MOVQ BX, CX 0x005b 00091 (/tmp/babel-ZDNznW/voZLcz.go:4) PCDATA $1, $0 0x005b 00091 (/tmp/babel-ZDNznW/voZLcz.go:4) NOP 0x0060 00096 (/tmp/babel-ZDNznW/voZLcz.go:4) CALL fmt.Print(SB) 0x0065 00101 (/tmp/babel-ZDNznW/voZLcz.go:5) ADDQ $72, SP 0x0069 00105 (/tmp/babel-ZDNznW/voZLcz.go:5) POPQ BP 0x006a 00106 (/tmp/babel-ZDNznW/voZLcz.go:5) RET 0x006b 00107 (/tmp/babel-ZDNznW/voZLcz.go:5) NOP 0x006b 00107 (/tmp/babel-ZDNznW/voZLcz.go:3) PCDATA $1, $-1 0x006b 00107 (/tmp/babel-ZDNznW/voZLcz.go:3) PCDATA $0, $-2 0x006b 00107 (/tmp/babel-ZDNznW/voZLcz.go:3) CALL runtime.morestack_noctxt(SB) 0x0070 00112 (/tmp/babel-ZDNznW/voZLcz.go:3) PCDATA $0, \(-1 0x0070 00112 (/tmp/babel-ZDNznW/voZLcz.go:3) JMP 0 0x0000 49 3b 66 10 76 65 55 48 89 e5 48 83 ec 48 44 0f I;f.veUH..H..HD. 0x0010 11 7c 24 20 48 8d 44 24 20 48 89 44 24 18 84 00 .|\) H.D$ H.D$… 0x0020 48 8d 15 00 00 00 00 48 89 54 24 20 48 8d 15 00 H……H.T$ H… 0x0030 00 00 00 48 89 54 24 28 84 00 eb 00 48 89 44 24 …H.T\((....H.D\) 0x0040 30 48 c7 44 24 38 01 00 00 00 48 c7 44 24 40 01 0H.D\(8....H.D\)@. 0x0050 00 00 00 bb 01 00 00 00 48 89 d9 0f 1f 44 00 00 ……..H….D.. 0x0060 e8 00 00 00 00 48 83 c4 48 5d c3 e8 00 00 00 00 …..H..H]…… 0x0070 eb 8e .. rel 2+0 t=23 type:int+0 rel 35+4 t=14 type:int+0 rel 47+4 t=14 main..stmp_0+0 rel 97+4 t=7 fmt.Print+0 rel 108+4 t=7 runtime.morestack_noctxt+0 go:cuinfo.producer.main SDWARFCUINFO dupok size=0 0x0000 2d 4e 20 2d 6c 20 72 65 67 61 62 69 -N -l regabi go:cuinfo.packagename.main SDWARFCUINFO dupok size=0 0x0000 6d 61 69 6e main main..inittask SNOPTRDATA size=8 0x0000 00 00 00 00 00 00 00 00 …….. rel 0+0 t=81 fmt..inittask+0 main..stmp_0 SRODATA static size=8 0x0000 e7 03 00 00 00 00 00 00 …….. runtime.nilinterequal·f SRODATA dupok size=8 0x0000 00 00 00 00 00 00 00 00 …….. rel 0+8 t=1 runtime.nilinterequal+0 runtime.gcbits.0200000000000000 SRODATA dupok size=8 0x0000 02 00 00 00 00 00 00 00 …….. type:.namedata.*[1]interface {}- SRODATA dupok size=18 0x0000 00 10 2a 5b 31 5d 69 6e 74 65 72 66 61 63 65 20 ..*[1]interface 0x0010 7b 7d {} type:[1]interface {} SRODATA dupok size=72 0x0000 10 00 00 00 00 00 00 00 10 00 00 00 00 00 00 00 ……………. 0x0010 6e 20 6a 3d 02 08 08 11 00 00 00 00 00 00 00 00 n j=………… 0x0020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ……………. 0x0030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ……………. 0x0040 01 00 00 00 00 00 00 00 …….. rel 24+8 t=1 runtime.nilinterequal·f+0 rel 32+8 t=1 runtime.gcbits.0200000000000000+0 rel 40+4 t=5 type:.namedata.*[1]interface {}-+0 rel 44+4 t=-32763 type:*[1]interface {}+0 rel 48+8 t=1 type:interface {}+0 rel 56+8 t=1 type:[]interface {}+0 runtime.memequal64·f SRODATA dupok size=8 0x0000 00 00 00 00 00 00 00 00 …….. rel 0+8 t=1 runtime.memequal64+0 runtime.gcbits.0100000000000000 SRODATA dupok size=8 0x0000 01 00 00 00 00 00 00 00 …….. type:*[1]interface {} SRODATA dupok size=56 0x0000 08 00 00 00 00 00 00 00 08 00 00 00 00 00 00 00 ……………. 0x0010 a8 0e 57 36 08 08 08 36 00 00 00 00 00 00 00 00 ..W6…6…….. 0x0020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ……………. 0x0030 00 00 00 00 00 00 00 00 …….. rel 24+8 t=1 runtime.memequal64·f+0 rel 32+8 t=1 runtime.gcbits.0100000000000000+0 rel 40+4 t=5 type:.namedata.*[1]interface {}-+0 rel 48+8 t=1 type:[1]interface {}+0 type:.importpath.fmt. SRODATA dupok size=5 0x0000 00 03 66 6d 74 ..fmt gclocals·g2BeySu+wFnoycgXfElmcg== SRODATA dupok size=8 0x0000 01 00 00 00 00 00 00 00 …….. gclocals·/ydTHfVJHvKeH/UP4dRKSQ== SRODATA dupok size=9 0x0000 01 00 00 00 06 00 00 00 00 ……… main.main.stkobj SRODATA static size=24 0x0000 01 00 00 00 00 00 00 00 d8 ff ff ff 10 00 00 00 ……………. 0x0010 10 00 00 00 00 00 00 00 …….. rel 20+4 t=5 runtime.gcbits.0200000000000000+0

↑これらは全く同じ結果になるので、iotaは事前に値を展開していることがわかる。Goではインスタンスや実行するまで値が決まらないものはconstに指定できないので、この動作は予想できる。少なくともGoアセンブリに変換する直前では単純なintになっているのだ。

コードを見てみる。↓constの宣言部分を処理する部分を見る。constはカッコを使ってグループで宣言でき、そのグループごとにループを回す。その1回分の処理が以下だ。グループごとにforループからとったindexがあって、enumの初期値に基づいてiotaの値がどうなるか計算している。だからiotaを1はじまりとかにできるわけ。

case *syntax.ConstDecl: top := len(check.delayed)

// iota is the index of the current constDecl within the group if first < 0 || s.Group = nil || list[index-1].(*syntax.ConstDecl).Group ! s.Group { first = index last = nil } iota := constant.MakeInt64(int64(index - first))

↓そして、作成したローカル変数iotaでconstを初期化する。対応した初期化式が与えられていれば、そちらで上書きする。

for i, name := range s.NameList { obj := NewConst(name.Pos(), pkg, name.Value, nil, iota) lhs[i] = obj

var init syntax.Expr if i < len(values) { init = values[i] }

check.constDecl(obj, last.Type, init, inherited) }

↓初期化式initを評価して、その結果を x に設定する。 x を使って、constの値を設定、初期化する。iotaキーワードが使われていれば初期化式initはnilのはずなので、評価が行われず x は変化しない。

var x operand if init != nil { if inherited { / The initialization expression is inherited from a previous / constant declaration, and (error) positions refer to that / expression and not the current constant declaration. Use / the constant identifier position for any errors during / init expression evaluation since that is all we have / (see issues go.dev/issue/42991, go.dev/issue/42992). check.errpos = obj.pos } check.expr(nil, &x, init) } check.initConst(obj, &x)

x の値を使って値をセットする。

lhs.val = x.val

↓ちなみにCheckerは、型チェックの状態を保持する構造体である。

/ A Checker maintains the state of the type checker. / It must be created with NewChecker. type Checker struct {

つまり、constにiotaキーワードを使った場合、型チェックの過程で実際の値が決まる、という感じになっている。

関連。