Go 的枚举很糟糕
Go 对枚举的处理——其实只是带有 `iota` 辅助的整数常量——被批评为与 Rust、Ada、Java,甚至 1970 年代的 Pascal 等语言中更丰富的枚举或和类型系统相比,过于原始且不安全。评论者争论 Go 是否应该加入真正的、类型安全的枚举,提供穷尽性检查和更安全的序列化,还是说它“直接用整数,必要时再用代码生成/Protobuf”的极简哲学,正是为了在普通 Web 和后端工作中保持简单与易用而做出的有意且可接受的权衡。讨论还延伸到更广泛的类型安全、错误处理,以及一门语言在增加特性与保持核心精简和有主见之间应走多远。
“枚举”究竟是什么(枚举 vs 和类型)
- 若干评论区区分了经典枚举(有限个具名常量的集合,通常有整数底层表示并支持自然迭代/排序)与代数/和类型(可以承载结构化数据的变体,且需要模式匹配)。
- Rust 的
enum反复被提到其实是和类型,而不是传统意义上的“真正枚举”,不过也有人认为这种区分在实践中并不太有用。 - 有些人认为把枚举和和类型强行归为一个概念会让人困惑,并在概念上有害;另一些人则更关注它们共同的“在多个选项中做选择”这一用途。
Go 目前的“枚举”现状
- Go 没有专门的 enum 关键字;惯用写法是
type T int再配合const (...)和iota。 - 批评点:
- 不是封闭集合;任何底层
int都能构造出来,包括非法值。 - 没有内置字符串表示、
switch的穷尽性检查,也无法在底层类型之外阻止不同“枚举”类型的混用。 - 有人认为
iota对序列化值很危险,因为重排常量会悄悄改变数值编码。
- 不是封闭集合;任何底层
- 辩护点:
- 很多人觉得
iota简洁而且非常方便,尤其适合位标志。 - 有人认为 Go 的“枚举”就是真正的枚举(区别类型中的具名常量),额外的护栏并不值得增加复杂度。
- 把任意整数传进来,在一些人看来显然是调用方的 bug,而不是类型系统失败。
- 很多人觉得
替代方案与工具
- 常见做法:使用代码生成器(
go generate、第三方工具、自定义 DSL)来补充字符串化、JSON/Protobuf 集成以及校验。 - 如果本来就使用 gRPC/Protobuf,Protobuf 枚举被认为是一个稳健的选择,包括安全演进和跨语言支持。
更广泛的语言设计争论
- 一方称赞 Go 的极简主义、快速构建、简单工具链,以及它对 REST/DevOps 工作的“蓝领”适配性。
- 另一方则称这门语言原始且“愚蠢地固执”,抗拒公认的特性(泛型、真正的枚举、和类型、空值安全、更丰富的类型约束)。
- 对比指出,许多更老或其他语言(Pascal、Ada、Rust、ML 语言、C#、Java)拥有更强的枚举或 ADT,在编译期穷尽性检查和与类型系统的整合上更好。
错误处理与可选性
- 有人认为 Go 显式返回
error很合理,把错误当作正常控制流。 - 另一些人更偏好
Result/Either/Option风格的和类型,并认为 Go 缺乏这类构造(再加上到处都是 nil 可空性)是一个重大的设计缺口。