go 枚举需用 const + 自定义类型 + iota 实现,必须显式声明类型(如 type status int)并为常量指定类型,否则失去类型安全;iota 按 const 块重置、逐行递增;string() 用于调试输出,marshaljson 才控制 json 序列化格式。

Go 语言没有 enum 关键字,所有“枚举”都靠 const + 自定义类型 + iota 组合实现;直接用 const 定义裸数字(比如 const Active = 1)等于放弃类型安全,不推荐。
const 块里不写类型,就等于埋雷
很多人写枚举时习惯这样:
const (
Pending = iota
Running
Done
)
问题在于:这三个常量都是 untyped int,不是独立类型。它们可以被赋给任何整型变量(int、int32、uint8),也能和任意整数做比较或运算——编译器不会拦你,但语义完全失控。
- 错误示范:
status := 42; if status == Pending { ... }——42是未类型化整数,能和Pending比较,毫无约束 - 正确做法:先定义类型,再绑定常量:
type Status int,然后在const块中显式指定类型:Pending Status = iota - 漏掉类型声明,后续结构体字段、函数参数、接口实现都会失去编译期校验
iota 不是自动递增的魔法,它只认 const 块和换行
iota 的行为非常机械:每个新的 const 块开头重置为 0,之后每出现一行(含空行?不,空行跳过;含注释?是,注释行也算一行)就加 1。
- 常见误判:
const ( _ = iota; A; B )→A是 1,B是 2,不是 0 和 1 - 想留出 0 表示无效值?用
_ = iota占位,但必须写在第一行,否则后续值偏移 - 跨多个
const块时,iota各自重置,不能指望全局连续 - 别在
const块外用iota—— 语法错误,iota只在const声明上下文中有效
String() 方法不是可选功能,是调试刚需
定义了 type Status int 和对应常量后,fmt.Println(status) 默认只输出数字,比如 1,根本看不出是 Running 还是别的什么。
- 必须手动实现
String()方法,接收者类型要和枚举类型完全一致:func (s Status) String() string - switch 分支必须覆盖所有合法值,
default分支建议返回带类型的 fallback(如"Status(unknown)"),避免静默空字符串 - 没实现
String(),日志、测试输出、pprof 标签全是一堆数字,排查成本陡增 - 注意:这个方法不影响
json.Marshal输出——JSON 仍默认序列化为数字,如需字符串需另写MarshalJSON
JSON 序列化输出数字还是字符串,得自己控制
即使你认真写了 type Status int 和 String(),json.Marshal(struct{ S Status }) 依然输出 {"S":1},因为 JSON 包不调用 String()。
- 要输出
{"S":"running"},必须实现MarshalJSON() ([]byte, error)方法 - 典型写法是 switch 枚举值,拼字符串并用
json.Marshal包一层,或直接返回[]byte(`"` + s.String() + `"`) - 反序列化要用
UnmarshalJSON配合字符串匹配,或接受数字再映射(注意容错,比如未知字符串应返回 error 而非静默转成 0) - 如果 API 同时支持数字和字符串输入,别图省事用
interface{},应在解码层统一处理,避免业务逻辑里到处判断类型
最易被忽略的一点:枚举值是否允许“未定义数字”参与运算。比如 Status(999) 是合法的 Status 类型值,但不在你定义的常量范围内——是否允许?这决定了要不要加 IsValid() 校验,尤其在反序列化或外部输入场景。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











