系列:Go 语言从入门到进阶
作者:耿雨飞
适用版本:go v1.26.2
前置条件
在开始本章学习之前,请确保:
- 已完成第 1 ~ 6 章的学习,掌握接口、类型断言和类型 switch
- 理解接口的隐式实现机制和方法集规则
- 已获取 Go 1.26.2 源码树(
go-go1.26.2目录)
导读
Go 1.18 引入了泛型(Generics),这是 Go 语言自诞生以来最大的语法扩展。在此之前,通用数据结构和算法只能通过interface{}或代码生成实现,要么牺牲类型安全,要么增加维护成本。泛型的引入让我们可以编写类型安全的通用代码,同时保持 Go 一贯的简洁风格。
Go 的泛型设计独具特色——它使用**类型约束(Type Constraints)**来限制类型参数的能力范围,而类型约束本身就是接口。这意味着泛型与 Go 已有的接口体系无缝融合,而非引入一套全新的概念。
本章将从泛型基础语法出发,逐步深入到类型约束的进阶用法,最后结合slices、maps、cmp三个标准库泛型包的源码,展示泛型在实际工程中的应用。
本章将对照 Go 1.26.2 源码中的以下关键路径:
| 源码路径 | 内容说明 |
|---|---|
src/builtin/builtin.go:97 | any约束的定义——interface{}的别名 |
src/builtin/builtin.go:104 | comparable约束的定义 |
src/cmp/cmp.go | cmp.Ordered约束、cmp.Compare、cmp.Less、cmp.Or函数 |
src/slices/slices.go | 泛型切片操作:Equal、Index、Contains、Insert、Delete、Clone、Compact、Reverse等 |
src/slices/sort.go | 泛型排序:Sort、SortFunc、BinarySearch、Min、Max等 |
src/maps/maps.go | 泛型映射操作:Equal、Clone、Copy、DeleteFunc |
src/maps/iter.go | 泛型映射迭代器:All、Keys、Values、Insert、Collect |
学习目标
完成本章后,你将能够:
- 理解类型参数和类型约束的语法,编写泛型函数和泛型类型
- 掌握
any和comparable两个内建约束的含义与适用场景 - 理解
~近似约束符的作用,能够定义自定义约束接口 - 从源码层面理解
cmp.Ordered约束的设计 - 熟练使用
slices、maps、cmp标准库泛型包 - 理解泛型与接口的关系,做出合理的设计选择
7.1 泛型基础
7.1.1 为什么需要泛型
在泛型引入之前,编写通用函数有两种选择,都不理想:
方式一:为每种类型重复编写
funcContainsInt(s[]int,vint)bool{for_,x:=ranges{ifx==v{returntrue}}returnfalse}funcContainsString(s[]string,vstring)bool{for_,x:=ranges{ifx==v{returntrue}}returnfalse}// 还有 ContainsFloat64、ContainsBool... 无穷无尽方式二:使用 interface{},牺牲类型安全
funcContains(s[]any,v any)bool{for_,x:=ranges{ifx==v{returntrue}}returnfalse}// 调用时需要手动转换类型,且编译器无法检查类型匹配// Contains([]any{1, 2, 3}, "hello") // 编译通过,但逻辑错误方式三(Go 1.18+):泛型——类型安全且通用
funcContains[E comparable](s[]E,v E)bool{for_,x:=ranges{ifx==v{returntrue}}returnfalse}// 编译期类型安全Contains([]int{1,2,3},42)// OKContains([]string{"a","b"},"b")// OK// Contains([]int{1, 2, 3}, "hello") // 编译错误!类型不匹配7.1.2 类型参数(Type Parameters)
类型参数是泛型的核心语法。在函数名或类型名后用方括号[]声明类型参数:
func函数名[T 约束](参数列表)返回值列表{// T 在函数体中可以当做类型使用}packagemainimport"fmt"// 单个类型参数funcPrint[T any](v T){fmt.Println(v)}// 多个类型参数funcPair[K,V any](k K,v V)string{returnfmt.Sprintf("(%v, %v)",k,v)}// 类型参数用于返回值funcZero[T any]()T{varzero Treturnzero}funcmain(){// 显式指定类型参数Print[int](42)Print[string]("hello")// 类型推断——编译器自动推断 TPrint(3.14)// T 推断为 float64Print(true)// T 推断为 boolfmt.Println(Pair("name",42))// (name, 42)fmt.Println(Zero[int]())// 0fmt.Println(Zero[string]())// ""fmt.Println(Zero[bool]())// false}类型推断规则:编译器根据传入的实参类型推断类型参数。大多数情况下无需显式指定类型参数,但在以下场景中需要显式指定:
- 类型参数只出现在返回值(如
Zero[int]()) - 需要消除歧义时
7.1.3 类型约束(Type Constraints)
类型约束限制了类型参数可以接受的类型范围。在 Go 中,类型约束就是接口:
// any 约束:接受任何类型funcPrint[T any](v T){...}// comparable 约束:接受所有可比较的类型(支持 == 和 !=)funcContains[E comparable](s[]E,v E)bool{...}// 自定义接口约束typeStringerinterface{String()string}funcPrintString[T Stringer](v T){fmt.Println(v.String())}7.1.4 any 约束
any是interface{}的别名,定义在src/builtin/builtin.go:97:
// src/builtin/builtin.go:96-97// any is an alias for interface{} and is equivalent to interface{} in all ways.typeany=interface{}any约束允许传入任何类型,但这也意味着在函数体内你只能对T做最基本的操作——赋值、取地址、作为any使用:
funcIdentity[T any](v T)T{returnv// OK:赋值和返回}funcBroken[T any](a,b T)bool{// return a == b // 编译错误!any 不保证支持 ==// return a + b // 编译错误!any 不保证支持 +_=a_=breturnfalse}7.1.5 comparable 约束
comparable是一个特殊的内建约束,定义在src/builtin/builtin.go:99-104:
// src/builtin/builtin.go:99-104// comparable is an interface that is implemented by all comparable types// (booleans, numbers, strings, pointers, channels, arrays of comparable types,// structs whose fields are all comparable types).// The comparable interface may only be used as a type parameter constraint,// not as the type of a variable.typecomparableinterface{comparable}comparable约束允许对类型参数使用==和!=运算符:
packagemainimport"fmt"funcIndexOf[E comparable](s[]E,v E)int{fori,x:=ranges{ifx==v{// comparable 保证了 == 可用returni}}return-1}funcmain(){fmt.Println(IndexOf([]int{10,20,30},20))// 1fmt.Println(IndexOf([]string{"a","b","c"},"b"))// 1fmt.Println(IndexOf([]float64{1.1,2.2,3.3},2.2))// 1// 以下不能编译——切片不是 comparable 类型// IndexOf([][]int{ {1}, {2}}, []int{1}) // 编译错误}comparable 涵盖的类型:
| 类型类别 | 示例 | 可比较 |
|---|---|---|
| 布尔 | bool | Yes |
| 数值 | int,float64,complex128 | Yes |
| 字符串 | string | Yes |
| 指针 | *int,*MyStruct | Yes |
| Channel | chan int | Yes |
| 数组 | [3]int(元素可比较时) | Yes |
| 结构体 | struct{X int}(字段都可比较时) | Yes |
| 切片 | []int | No |
| Map | map[string]int | No |
| 函数 | func() | No |
7.2 泛型函数与泛型类型
7.2.1 泛型函数定义与调用
泛型函数可以有复杂的类型参数和约束组合:
packagemainimport"fmt"// 返回两个值中较大的那个funcMax[Tinterface{~int|~float64|~string}](a,b T)T{ifa>b{returna}returnb}// 过滤切片:返回满足条件的元素funcFilter[T any](s[]T,ffunc(T)bool)[]T{varresult[]Tfor_,v:=ranges{iff(v){result=append(result,v)}}returnresult}// Map 转换:将 []T 转为 []UfuncMap[T,U any](s[]T,ffunc(T)U)[]U