Go 标准库到现在都没有一个正经的 Set 类型。想用集合,官方给的答案十几年没变:自己拿 map[T]boolmap[T]struct{} 凑一个。

这个状态可能要在 Go 1.28 结束。一个叫 Go Collections 的工作组,最近把一份总括性提案摆上了台面:泛型 Set、自定义哈希 Map、有序 Map、新版 Heap,一次性打包提交。

工作组成立不久,核心成员里能看到 Robert GriesemerIan Lance Taylor 这些参与过 Go 语言核心设计的名字。他们等的不是灵感,是语言能力补齐——泛型(Go 1.18)、迭代器(Go 1.23)到位之后,库定义的类型才第一次有可能跟内建类型一样顺手。这次提案,是把这两块拼图正式用起来的第一次系统尝试。

这次到底要加什么

提案覆盖四个独立子提案,外加一份已经落地的基础设施。

包名解决什么当前状态谁会用到
hash/maphash.Hasher自定义哈希与等价关系的标准接口已发布(Go 1.27)需要自定义键比较逻辑的库作者
container/hash.Map/Set不可比较类型(如切片)做键提案+实现CL,目标 1.28处理复杂键结构的开发者
container/set.Set可比较元素的标准集合提案+实现CL,目标 1.28大多数日常需要 Set 的场景
container/ordered.Map有序映射,支持范围查询提案+实现CL,目标 1.28(平衡二叉树实现)需要有序遍历、范围查询的场景
container/heap/v2.Heap重写现有 heap 接口提案+实现CL,目标 1.28用堆做优先队列的开发者

除了 maphash.Hasher,其余都还停在提案阶段,目标合入 Go 1.28,不是已经定论。Go 的提案走到这一步,被改甚至被搁置都不稀奇,这次也可能一样。

Go 走到这一步,用了几个版本 Go 1.18 泛型落地 Go 1.23 range-over-func 迭代器 Go 1.27 maphash.Hasher 发布 Go 1.28 集合库提案(未定) 三块语言能力凑齐,才轮到今天这份提案

两处克制,比新增类型更值得读

提案里最扎眼的不是加了什么,是明确写下了不做什么。

第一处:初版只保证 API 正确和渐进复杂度(asymptotic performance),常数级优化明确排除在这轮之外。这批 Set、Map、Heap 先求对,不求快,调优留到以后。

第二处更关键:文档里提到 CollectionSetMap 三个抽象约束接口,用来解释各个类型的方法为什么长得像。但这三个接口目前不导出,也不属于任何提案。

原因说得很直白:每个 Set 的 Union 方法签名都绑死自己的具体类型,天生互不兼容,这是经典的 binary method problem。要表达一个抽象 Set 概念,得靠递归约束或 F-bounded 多态,一旦公开导出,就是长期维护的公共契约。团队选择先内部用着攒经验,不急着许诺。

这些取舍里能看到具体的历史教训。Set 用 With 后缀区分纯函数版和原地修改版,是从 math/big.Int 早年的问题里学的。DeleteFunc 留在接口里没删,是因为省掉它会让树结构删除从 O(n) 退化成 O(n log n)。这种细节,比“加了个 Set”本身更值得工程师读一遍原文。

谁该关心,现在该做什么

三类人跟这份提案关系最近。

日常写业务代码的 Go 工程师:现在不用改任何东西。提案还在走流程,Go 1.28 没有发布时间表,标准 Set 用上之前,map[T]struct{} 该怎么写还怎么写。

自己包装过 Set、OrderedMap 工具库的库作者:值得盯紧的是那三个未导出的抽象接口。一旦哪天正式公开,就意味着所有实现要对齐同一套方法签名,现在跟进社区讨论,比事后重构划算。

长期靠 golang.org/x/exp 或自建容器包过日子的团队:标准库补齐之后,这类自建方案的必要性会打折扣,但现在还谈不上迁移,先看提案能不能真的落进 Go 1.28。

Go 早年的哲学是语言够灵活,slice 和 map 就够用,这句话说了十几年,几乎成了固定说辞。这次松口,不是哲学变了,是工具终于配齐了——没有泛型和迭代器,硬做一个 Set,只会是个半吊子库类型。真正该盯的不是这几个新包,是那份还没导出的抽象接口最终会不会公开。公开了,Go 才算有一套集合语言,不是几个孤立类型拼在一起。