Goodbye core types-Hello Go as we know and love it!

Mar 28, 2025·
王昊
王昊
· 1 min read

📌 一、理解背景:Go语言泛型(Generics)的引入

在 Go 1.18(2022年3月发布)以前,Go语言并没有泛型功能(generics)。所谓泛型就是写函数或数据结构时,可以不指定具体类型,而用类型参数代替,然后调用时再填入具体类型,这样函数可以用于很多不同类型的情况。

比如:

// 没有泛型之前,得为每种类型写一个函数
func SumInt(a, b int) int { return a + b }
func SumFloat(a, b float64) float64 { return a + b }

// 泛型后,可以只写一个函数:
func Sum[T int | float64](a, b T) T { return a + b }

泛型的引入使Go语言更灵活、更强大。但随之而来,语言内部实现变得复杂了。


📌 二、理解类型参数、类型约束和类型集

泛型中出现了一些重要概念:

  • 类型参数(Type Parameter)

    就是泛型函数或泛型结构体中用来代替具体类型的占位符,类似函数参数。

    func Example[T any](value T) T {
        return value
    }
    

    这里T就是一个类型参数。

  • 类型约束(Type Constraint)

    表示一个类型参数能取哪些具体类型的限制。用接口表示:

    type MyConstraint interface {
        ~string | ~[]byte
        Hash() uint64
    }
    

    这个约束表示任何底层类型是string[]byte,并且实现了Hash()方法的类型都可以满足。

  • 类型集(Type Set)

    一个类型约束定义了一组满足特定条件的类型集合,称为“类型集”。上述例子中,类型集就是满足“底层类型是string[]byte且带有Hash()方法”的所有类型的集合。


📌 三、“核心类型”(Core Type)到底是什么?

引入泛型之后,Go的实现者发现了一个问题:

以前语言规范里,很多操作(比如closeappend、切片操作)都是直接说:“参数必须是通道”、“参数必须是切片或数组”。
但泛型出现后,有些操作的参数可能是类型参数,无法直接说清楚具体类型了。

为了解决这个问题,语言规范引入了一个抽象概念,称为核心类型(core type)

核心类型的定义大致是:

  • 非泛型情况:某个普通类型的核心类型就是它的底层类型(underlying type)
    比如:type MyInt int 的核心类型就是int

  • 泛型情况:若类型参数的类型集中的所有类型的底层类型都完全一样,则这个类型参数的核心类型就是那个共同的底层类型。
    反之,如果类型集中有不同的底层类型,则不存在核心类型。

例如:

  • interface{ ~[]int }类型集只有一种底层类型([]int),因此它的核心类型就是[]int
  • interface{ ~[]byte | ~string }存在两种不同的底层类型([]bytestring),因此这个接口不存在核心类型。

最初引入核心类型的原因很简单:就是为了语言规范方便叙述和编译器方便实现,避免处理泛型参数时总是考虑一堆类型的集合。


📌 四、核心类型存在的问题(为何要去掉?)

虽然核心类型初衷是为了简化泛型的实现,但实际使用中却带来了一些新问题:

  • 问题1:规则过于严格,限制了语言扩展

    因为核心类型要求类型集中所有类型必须有相同的底层类型,很多合理的泛型操作反而被禁止了。

    例如,下面的约束就无法获得核心类型:

    type Constraint interface {
        ~string | ~[]byte
        Hash() uint64
    }
    

    这样,使用泛型参数时,虽然直观上对字符串和字节数组的某些操作类似,但在核心类型的严格要求下无法实现。

  • 问题2:增加了语言复杂性

    很多简单的操作,比如:

    close(ch) // 以前只要求 ch 是 channel 即可
    

    现在规范里却变成了:

    “参数的核心类型必须是通道类型”

    这让普通的程序员在学习和理解非泛型代码时,也需要理解这个抽象而复杂的“核心类型”概念,给语言的学习造成负担。

  • 问题3:导致了规则的不统一

    因为存在核心类型的概念,某些正常的操作反而成为了语言规范的“特例”,比如len()cap()、索引操作。

    这种不统一性,反而使得语言变得更难懂、更难维护。

因此,核心类型在实践中变成了一个“鸡肋”,不仅没有真正让语言变简单,反而变复杂了。


📌 五、Go 1.25 做出了哪些改变?

基于以上问题,Go语言开发团队在即将到来的Go 1.25(2025年8月)中决定彻底废弃“核心类型”概念

  • 直接删掉了核心类型的定义

  • 规范中的每个操作,回到更直接的表述方式:

    比如,原来:

    “close函数的参数核心类型必须是通道”。

    现在恢复为:

    “close函数的参数必须是通道”。

  • 针对泛型情况下,规范增加额外但直接明确的说明:

    “若close函数的参数是类型参数,则类型集内所有类型必须为元素类型一致的通道类型。”

实际效果是

  • 语言规范变简单了:普通程序员不再需要理解抽象的“核心类型”,语言回归直观和简单。
  • 非泛型代码回归原样:不用再关心泛型相关的概念,操作的含义变得直接易懂。
  • 泛型操作更灵活:未来可以更灵活地拓展,比如允许更丰富的类型操作,不再受核心类型概念的限制。

📌 六、总结与直观理解

  • 泛型引入后 → 为了方便描述 → 出现了“核心类型”
  • 实际使用后发现 → 核心类型不仅没简化,反而带来了复杂性
  • Go 1.25决定 → 删掉“核心类型”,回归简单直接的语言设计方式

简单来说,核心类型原本是为了解决泛型实现中遇到的问题,但实际带来了更大的复杂性。Go团队决定“回到初心”,取消这个概念,让语言规范更加易懂,更便于未来扩展。


到这里,相信你已经完全理解了这篇文章的来龙去脉以及为何 Go 决定废弃“核心类型”的原因。