本文永久链接 – https://tonybai.com/2026/07/29/go-1-28-generic-collections-proposal
大家好,我是Tony Bai。
【导读】
Go 语言从诞生起就以“简单”著称,但也因此在集合类型上长期缺课——没有原生 Set,没有有序 Map,连堆都难用。就在昨天,由 Jonathan Amsterdam、Alan Donovan、Robert Griesemer 等七位 Go 核心贡献者组成的“Go Collections 工作组”正式公布了一份重磅提案集,计划为 Go 1.28 标准库引入哈希 Set/Map、树形有序 Map、新一代堆,以及一套解决“二元方法问题(binary method problem)”的抽象集合接口。这可能是 Go 泛型落地以来对标准库影响最大的一次改造。
【文章要点】
- 七位核心大佬联名:由 Robert Griesemer、Ian Lance Taylor 等 7 位 Go 核心贡献者组成的 Go Collections 工作组,正式在 Issue #80590 中抛出“伞形提案”,系统性补齐 Go 标准库集合类型。
- 7 大组件全景覆盖:一口气推出通用哈希 Map/Set (
container/hash)、标准规范 Set (container/set)、有序树形 Map (container/tree) 以及全新泛型堆 (container/heap/v2),彻底终结手写map[T]struct{}时代。 - 兼容存量代码:专门提供
container/mapset辅助包,让不便重构的数据结构(如传统的map[T]bool)也能无缝享受集合运算。 - 攻克理论难题:运用 F-bounded 多态(递归约束接口)首次给出了破解“二元方法问题”的抽象集合接口设计(
_AbstractSet/_AbstractMap),统一了集合 API 的规范。 - 极致工程取舍:严格区分纯函数式集合代数与原置修改(带有
-With后缀),平衡表达式易用性与内存分配效率;未来还将陆续推出插入序 Map 与 Stack。

Go 集合类型的“先天缺陷”
熟悉 Go 的开发者都知道一个尴尬的事实:这门语言的标准库几乎没有像样的集合类型(Collections)。
Go 的哲学一直是“把灵活性留给内置的 slice 和 map”。但代价也很明显:
- 想要一个 Set?对不起,只能自己用
map[T]bool或map[T]struct{}模拟,前者还有“false 到底算不算存在”的语义歧义; - 想要一个有序 Map?标准库里完全没有基于平衡树的实现;
- 想用堆?
container/heap是有的,但接口设计出了名的反人类,几乎每次用都要重新看一遍文档。
这种局面维持了十几年,核心原因是 Go 在 1.18 版本之前没有泛型,写一个通用的、类型安全的集合库几乎不可能。2022 年 Go 1.18 引入泛型、2023 年 Go 1.23 引入迭代器(iterator)之后,情况才真正发生变化——库作者终于有能力写出和内置类型体验相当的集合类型。
这也是这次提案诞生的技术前提。
Go Collections 工作组:七位大牛联手补课
2025 年底,Go 团队内部成立了一个名为 Go Collections working group 的专项小组,目标很明确:把常见的集合数据结构带进标准库,同时延续 Go 一贯的“实用主义 + 简单性”原则。
按姓氏字母顺序,工作组成员包括:
- Jonathan Amsterdam(@jba)
- Alan Donovan(@adonovan)
- Robert Griesemer(@griesemer)
- Daniel Martí(@mvdan)
- Roger Peppe(@rogpeppe)
- Keith Randall(@khr)
- Ian Lance Taylor(@ianlancetaylor)
这份名单的分量不用多说——Griesemer 是 Go 语言的联合设计者之一,Ian Lance Taylor 是 Go 泛型设计的主要推动者,其余几位也都是标准库和编译器领域的常年核心贡献者。
经过近一年的打磨,工作组在 GitHub issue #80590 中以“伞形提案”(umbrella proposal)的形式,一次性亮出了全部成果,并链接到各个具体子提案和对应的实现 CL(Change List)。
提案全景:七个组件分别解决什么问题
这份提案一共涉及七个关联条目,覆盖了从底层哈希机制到高层集合语义的完整链路:
| 提案 | 内容 | 状态 |
|---|---|---|
| #70471 | hash/maphash.Hasher:自定义哈希函数与等价关系的标准接口 |
已随 Go 1.27 发布 |
| #69559 | container/hash.Map[K,V]:基于哈希的通用 Map |
提案中 |
| #80584 | container/hash.Set[T]:基于哈希的通用 Set |
提案中 |
| #69230 | container/set.Set[T]:面向可比较元素的规范 Set 类型 |
提案中 |
| #77052 | container/mapset:为“传统 Set”(map[T]bool)提供集合运算的辅助函数包 |
提案中 |
| #60630 | container/tree.Map[K,V]:基于平衡二叉树的有序 Map |
提案中 |
| #77397 | container/heap/v2.Heap:泛型二叉堆,替代难用的旧版 heap |
提案中 |
几个值得展开的细节:
hash/maphash.Hasher 是整套体系的地基。它定义了一套标准接口,用来表达自定义的哈希函数与等价关系,这些关系可能不同于编译器为 map[K]V 默认生成的那一套——比如 key 类型本身不可比较(切片、map),或者默认比较结果并不是我们想要的(例如 go/types 包里 types.Type 需要用 types.Identical 做深度比较)。它的官方文档里甚至给出了一个布隆过滤器(Bloom filter)的示例。
container/set.Set[T] 大概率会成为“未来 Go 新 API 里的标准 Set”。它在底层透明地用 map[T]struct{} 实现,支持并集、交集等全部常见集合操作,比“遗留 Set”写法更方便,也彻底避免了 map[T]bool 里“false 到底是不存在还是显式为 false”的歧义。
container/mapset 则是给存量代码准备的过渡方案:如果一个项目的 API 已经固化为 map[T]bool,没法直接换成 set.Set,那么可以用 mapset 包里的一组辅助函数(Union、Intersection 等)在不改变数据结构的前提下获得集合语义,这些函数与 set.Set 的方法一一对应。
container/tree.Map[K,V] 补上了有序 Map 的空白。日常写法里“先建 map、再对 key 排序”在大多数场景下够用,但一旦涉及范围查询(range query),树形结构的性能优势就会体现出来。
container/heap/v2.Heap 是对旧版 container/heap 的彻底重做,目标是让泛型堆用起来不再需要“每次都重新学一遍接口”。
工作组也提前声明:接下来还会讨论 插入序哈希 Map(insertion-ordered hash map) 和 Stack 类型,只是这次没有一并放出。
核心难题:二元方法问题与抽象集合接口
这份提案里技术含量最高的部分,是如何用 Go 的类型系统表达一个“抽象的 Set”或“抽象的 Map”。
问题出在哪?如果每种具体的 Set 类型 S 都有一个形如:
func (S) Union(S) S
的方法,那么不同 Set 类型之间的 Union 方法互不兼容,没法用一个普通接口统一描述——这就是编程语言理论里经典的“二元方法问题”(binary method problem)。要表达这种抽象类型,只能借助 F-bounded 多态,也就是递归定义的约束接口。
工作组在 CL 761460 中,向 container 包添加了三个非导出的抽象约束接口:_AbstractCollection、_AbstractSet、_AbstractMap。核心结构大致如下:
// _AbstractCollection 建模一个元素类型为 E 的集合 C,
// 例如 *hash.Map、*hash.Set、*ordered.Map 或 set.Set。
type _AbstractCollection[E any, C _AbstractCollection[E, C]] interface {
Clear()
Clone() C
Contains(E) bool
ContainsAll(iter.Seq[E]) bool
Len() int
String() string
}
在此基础上,_AbstractMap 和 _AbstractSet 分别扩展出映射和集合特有的方法,比如 Get、Set、Union、Intersection 等。
这套接口目前故意不导出,只是作为文档,用来统一各具体集合类型的 API 惯例。工作组明确表示:暂不打算公开发布这些抽象类型,会先观察具体集合类型的实际使用情况再决定。与此同时,开发者可以按需自定义“最小约束接口”,比如提案文档里给出的这个例子——一个从抽象 Set 里随机取出并删除一个元素的通用函数:
// _TakeSet 定义了 Take 函数所需的最小抽象集合能力。
type _TakeSet[E any, S _TakeSet[E, S]] interface {
All() iter.Seq[E]
Delete(E) bool
}
// Take 从集合中移除并返回一个任意元素。
// 如果集合为空,返回零值。
func Take[S _TakeSet[E, S], E any](set S) (e E, found bool) {
for e = range set.All() {
found = true
set.Delete(e)
break
}
return
}
这种“按需定义最小接口”的思路,本质上是把“要不要把某个操作放进标准接口”这个决策,尽可能推迟到有足够实践经验之后再做。
设计取舍背后的工程哲学
提案文档花了相当篇幅解释一些“看似琐碎”但影响深远的设计决定,这些细节很能体现 Go 团队一贯的工程风格:
能返回更多信息就返回更多信息,减少重复查找。 比如大多数修改类方法都会返回“这次操作是否真的改变了集合大小”;Map.Set 和 Map.Delete 会返回旧的 key(如果存在)加一个布尔值,用来区分“key 本来就存在”和“key 的值恰好是零值”这两种情况。
Get 是 At 的变体,At 是为了在表达式里用起来更方便而存在的。 这是一个很典型的“为了少数高频场景专门开一个口子”的例子。
Map 没有 Equal 方法,因为 map 的 value 可能本身不可比较,这个限制是 Set 所没有的。
要不要把 Subset(Set) bool 放进 Set 接口? 工作组的结论是不放。理由是:有序集合可以在两个操作数区间不相交时快速判否,但即便如此,常见情况下 Subset 测试通常仍是 O(n),所以最终决定把它作为“抽象操作”留给用户自己组合,而不是塞进核心接口。作为对比,DeleteFunc 被保留了下来,因为如果没有它,对树形结构做条件删除,复杂度会从 O(n log n) 退化到更差的水平——这是一个“会不会影响渐近复杂度”的硬指标,而不是主观偏好。
集合代数操作是纯函数式的。 Union、Intersection 等操作返回一个新集合,不修改原集合;每个操作都配一个 -With 后缀的变体(如 UnionWith),效果是原地修改左操作数、不返回结果。前者“用起来方便”,后者“分配效率更高”。工作组特别提到,他们参考了 math/big.Int API 的历史经验,特意没有把两种语义合并成一个方法,以避免误用导致的“忘记接收返回值”或“意外修改”问题。
KeySetView[M, K, V] 之类的包装类型也是可行的设计——用一个 Map 的 key 集合去满足 Set 接口,只是往这种“视图 Set”里插入元素在语义上没有意义,因此必须 panic。
文档最后还专门做了一次“过度设计自检”:把 _AbstractMap 的每个方法都和现有 maps 包做了逐条对照,发现五个方法(Clone、DeleteFunc、All、Keys、Values)完全对应,一个方法(SetAll)换了个名字(maps.Insert),剩下的全部可以由内置运算符(clear、len、delete、下标访问)直接实现。基于这个对照,工作组顺带提出,或许值得给 maps 包补上 Contains、ContainsAll、DeleteAll 这几个目前缺失的辅助函数。
对 Go 开发者意味着什么
如果这批提案顺利通过并进入 Go 1.28,对日常开发会有几个直接影响:
- 不用再手写
map[T]struct{}模拟 Set 了,set.Set[T]会提供开箱即用、语义清晰的集合运算; - 有序遍历不用再“建 map + 排序”,
tree.Map能原生支持范围查询; - 自定义哈希逻辑有了标准接口,写布隆过滤器、缓存等对哈希语义有特殊要求的组件会更规范;
- 写通用集合算法的库作者多了一套“抽象约束接口”的参考范式,即便这套接口暂不公开,其设计思路(F-bounded 多态、Take/Subset 这类操作外置)本身就值得借鉴;
- 存量代码不用被迫重构:
container/mapset专门为“API 已固化、改不动”的老项目提供了平滑过渡路径。
当然,目前这些都还处于提案阶段,具体 API 是否会有调整、能否如期进入 Go 1.28,仍取决于社区讨论和 Go 团队的最终审议。
小结:提案尚未落地,值得持续关注
提案文档结尾提到,工作组预计还会陆续讨论更多提案,其中明确点名的包括插入序哈希 Map(insertion-ordered hash map)和 Stack。这意味着这次公布的七个提案,很可能只是 Go 标准库集合类型“系统性补课”的第一阶段。
对于一门以“克制”著称、十几年不轻易往标准库里加集合类型支持的语言来说,这次由七位核心开发者联署、一口气抛出七个关联提案的动作,规格不可谓不高。接下来能不能顺利落地 Go 1.28,值得持续关注。
原始提案地址:https://github.com/golang/go/issues/80590
本文根据 Go 官方 GitHub Issue #80590 整理编译,如有表述不准确之处,欢迎指正。
还在为写 Agent 框架频频死循环、上下文爆炸而束手无策?我的新专栏 《从0 开始构建 Agent Harness》 将带你:
- 抛弃臃肿框架,回归“驾驭工程 (Harness Engineering)”的第一性原理
- 用 Go 语言手写 ReAct 循环、并发拦截与上下文压缩引擎等,复刻极简OpenClaw
- 构建坚不可摧的 Safety Middleware 与飞书人工审批防线
- 在底层实现 Token 成本审计、链路追踪与自动化跑分评估
- 从“调包侠”进化为掌控大模型边界的“AI 操作系统架构师”
扫描下方二维码,开启从 0 开始构建Agent Harness 的实战之旅。

你的Go技能,是否也卡在了“熟练”到“精通”的瓶颈期?
- 想写出更地道、更健壮的Go代码,却总在细节上踩坑?
- 渴望提升软件设计能力,驾驭复杂Go项目却缺乏章法?
- 想打造生产级的Go服务,却在工程化实践中屡屡受挫?
继《Go语言第一课》后,我的《Go语言进阶课》终于在极客时间与大家见面了!
我的全新极客时间专栏 《Tony Bai·Go语言进阶课》就是为这样的你量身打造!30+讲硬核内容,带你夯实语法认知,提升设计思维,锻造工程实践能力,更有实战项目串讲。
目标只有一个:助你完成从“Go熟练工”到“Go专家”的蜕变! 现在就加入,让你的Go技能再上一个新台阶!

商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求,请扫描下方公众号二维码,与我私信联系。
