golang iota

Golang iota

iota 是 Go 在 const 声明中提供的编译期常量生成器。它最常用于定义枚举,但也可以参与位移、乘法、加法和掩码等常量表达式,生成一组有规律的值。

一、iota 的定义

Go 规范将 iota 定义为预声明标识符。它表示连续的、无类型的整数常量

const (
	c0 = iota // c0 == 0
	c1 = iota // c1 == 1
	c2 = iota // c2 == 2
)

这个 const 声明中有三个 ConstSpec,因此它们的索引分别是 0、1、2

后一项可以省略表达式,所以通常简写为:

const (
	c0 = iota
	c1
	c2
)

这里的 c1c2 并不是特殊的“自动加一”语法,而是 Go 的隐式重复规则:在括号形式的 const 声明中,省略表达式列表时,会重复前一个非空 ConstSpec 的表达式列表及其类型。因此它等价于前一个完整版本。

二、iota 如何计数

1. 每个 const 声明都从 0 开始

iota 只在当前常量声明中计数。新的 const 声明会重新从 0 开始:

const (
	a = iota // a == 0
	b = iota // b == 1
)

const c = iota // c == 0
const d = iota // d == 0

因此,多个独立的 const 声明不会共享一个连续的 iota 序列。

2. 按 ConstSpec 递增

ConstSpec 可以理解为常量声明列表中的一项。iota 按项递增,而不是按名称或表达式递增

const (
	a, b = iota, iota + 10 // 一个 ConstSpec,iota == 0
	c, d = iota, iota + 10 // 一个 ConstSpec,iota == 1
)

虽然每项声明了两个常量,但 iota 只递增一次。

空行和注释也不会额外增加计数;计数依据是语法结构,而不是代码中的行号。

3. 同一个 ConstSpec 中的 iota 值相同

const (
	row, column = iota, iota + 10 // row == 0, column == 10
	nextRow, nextColumn           // nextRow == 1, nextColumn == 11
)

同一个 ConstSpec 中的所有 iota 都使用同一个索引,不会因为出现了两次而分别取不同的值。

三、使用 iota 定义枚举

1. 普通枚举

type Weekday int

const (
	Sunday Weekday = iota
	Monday
	Tuesday
	Wednesday
	Thursday
	Friday
	Saturday
)

它等价于:

const (
	Sunday    Weekday = 0
	Monday    Weekday = 1
	Tuesday   Weekday = 2
	Wednesday Weekday = 3
	Thursday  Weekday = 4
	Friday    Weekday = 5
	Saturday  Weekday = 6
)

通常建议为枚举定义一个新的类型,而不是直接使用 int。这样可以让编译器帮助区分不同语义的整数。

2. 跳过第一个值

如果 0 表示“未设置”或“未知”,可以用空白标识符 _ 跳过它:

type Status int

const (
	_ Status = iota // 0:保留给未设置状态
	StatusReady     // 1
	StatusRunning   // 2
	StatusStopped   // 3
)

_ 不会绑定一个可引用的常量名,但这一项仍然占用 iota == 0,后面的常量从 1 开始。

3. 从其他值开始

iota 只是参与表达式计算,并不要求结果从 0 开始:

const (
	HTTPContinue = 100 + iota // 100
	HTTPOK                    // 101
	HTTPAccepted              // 102
)

四、利用表达式生成规律值

1. 位标志

iota 与左移运算结合,可以生成互不重叠的二进制位:

type Permission uint32

const (
	PermissionRead Permission = 1 << iota // 1
	PermissionWrite                       // 2
	PermissionDelete                      // 4
)

使用时可以通过按位或组合多个权限:

permissions := PermissionRead | PermissionWrite

2. 位掩码

同一个 ConstSpec 中可以同时生成位值和掩码:

const (
	bit0, mask0 = 1 << iota, 1<<iota - 1 // 1, 0
	bit1, mask1                         // 2, 1
	bit2, mask2                         // 4, 3
)

这里的 1<<iota - 1 等价于 (1 << iota) - 1,因此掩码依次是 0、1、3

3. 容量单位

官方 Wiki 使用 iota 定义字节容量单位:

type ByteSize float64

const (
	_           = iota // 跳过 0
	KB ByteSize = 1 << (10 * iota)
	MB
	GB
	TB
	PB
	EB
	ZB
	YB
)

KBiota1,因此 KB == 1<<10MB == 1<<20GB == 1<<30。这说明 iota 并不只是枚举计数器,而是可以参与任意合法的常量表达式。

五、显式表达式不会暂停计数

某一项不使用 iota,也不会让后面的索引停留在原处:

const (
	a = 1 << iota // a == 1,iota == 0
	b = 1 << iota // b == 2,iota == 1
	c = 3         // c == 3,iota == 2,但没有使用
	d = 1 << iota // d == 8,iota == 3
)

c 仍然占据索引 2,所以 d 使用的是 3,结果是 1 << 3 == 8,而不是 1 << 2 == 4

如果想明确跳过一个位,通常写成:

const (
	FlagA = 1 << iota // 1
	_                 // 2:保留但不使用
	FlagC             // 4
)

六、类型规则

1. 默认是无类型整数常量

const (
	u = iota * 42 // 无类型整数常量,值为 0
	v = iota * 42 // 无类型整数常量,值为 42
	w = iota * 42 // 无类型整数常量,值为 84
)

由于表达式只包含无类型常量,结果也保持为无类型整数常量。它在需要具体类型的上下文中才会被转换:

var n int64 = v

2. 显式类型会作用于当前 ConstSpec

const (
	u         = iota * 42
	v float64 = iota * 42
	w         = iota * 42
)

此时 uw 是无类型整数常量,vfloat64 常量。类型声明不会自动影响其他 ConstSpec

定义枚举时,通常把类型写在第一项,让后续省略表达式的项继承它:

type Level int

const (
	LevelLow Level = iota
	LevelMedium
	LevelHigh
)

七、隐式重复规则

1. 重复的是表达式,不是上一次的结果

const (
	a = 10 * iota // a == 0
	b              // b == 10
	c              // c == 20
)

bc 不是复制 a 的结果 0,而是复制表达式 10 * iota,再使用各自 ConstSpeciota 重新计算。

2. 只能在括号形式的 const 声明中省略

下面的代码不会形成连续序列:

const A = iota // A == 0
const B = iota // B == 0,不是 1

独立的单行声明之间不会共享上一条表达式列表。

3. 表达式数量必须匹配

const (
	a, b = iota, iota + 10
	c, d                // 合法:仍然是两个表达式
)

省略表达式时,会重复前一个非空列表,因此左侧标识符的数量必须与表达式数量匹配。

八、常见错误

错误 1:把 iota 当成全局计数器

实际情况是每个 const 声明都会重置:

const A = iota // 0
const B = iota // 0

错误 2:以为固定值不会占用索引

c = 3 虽然没有使用 iota,但仍然是一个 ConstSpec,所以会占用一个索引。

错误 3:把一行中的多个常量当成多个索引

const (
	a, b = iota, iota // a、b 都使用 0
	c, d = iota, iota // c、d 都使用 1
)

错误 4:在中间插入枚举项

const (
	A = iota // 0
	B       // 1
	C       // 2
)

如果在 AB 之间插入新项,原来的 BC 都会改变。对于需要持久化、网络传输或兼容历史数据的值,这可能造成严重问题。

错误 5:忽略类型差异

const (
	a       = iota // 无类型整数
	b float64 = iota
	c       = iota // 无类型整数
)

无类型常量和有类型常量在赋值、函数调用和接口使用时的行为不同。需要稳定枚举类型时,应在第一项明确指定自定义类型。

错误 6:把 iota 当成运行时变量

iota 是常量声明中的编译期常量生成器,不是运行时计数器。它适合生成固定值,不适合表达运行过程中变化的计数。

总结

iota 的核心规则可以浓缩为以下几点:

  • iota 是预声明的、无类型的整数常量;
  • 每个 const 声明都从 0 开始;
  • 它按 ConstSpec 递增,而不是按名称或表达式递增;
  • 同一个 ConstSpec 中的多个 iota 使用具有相同的值;
  • 不使用 iota 的项也会占用索引;
  • 省略表达式时,重复的是上一条非空表达式列表及其类型;
  • iota 可以参与位移、乘法、加法和掩码等常量表达式;
  • 对需要稳定外部编号的常量,显式数值往往更稳妥。

参考资料

评论