Go - Type Constraint
限制
- 对泛型函数的类型参数以及泛型函数中的实现代码
设置限制- 泛型函数的调用者只能传递
满足限制条件的类型实参 - 泛型函数内部也只能以类型参数允许的方式使用这些类型实参值
- 泛型函数的调用者只能传递
- 在 Go 中,使用类型参数
约束来表达这种限制条件- 函数
普通参数在函数实现中可以表现出来的性质与可以参与的运算由参数类型限制 - 泛型函数的
类型参数由约束来限制
- 函数
内置约束
any
最宽松的约束
- 无论是
泛型函数还是泛型类型,其所有的类型参数声明中都必须显式包含约束 - 可以使用
空接口类型来表达所有类型
1 | func foo[T interface{}](sl []T) {} |
空接口类型的不足:使得声明变得
冗长、复杂、语义不清
1 | // any is an alias for interface{} and is equivalent to interface{} in all ways. |
any 约束的类型参数意味着可以接受
所有类型作为类型实参,而在函数体内,可以执行
- 声明变量
- 同类型赋值
- 将变量传给其它函数或者从函数返回
- 取变量地址
- 转换或者赋值给 interface{} 类型变量
- 用在 type switch 或者 type assertion 中
- 作为复合类型中的元素类型
- 传递给预定义的函数,如 new
1 | func foo[T1, T2 any](t1 T1, t2 T2) T1 { |
any 约束不支持
比较
1 | func foo[T1, T2 any](t1 T1, t2 T2) T1 { |
comparable
编译器会在
编译期间判断某个类型是否实现了 comparable 接口
1 | // comparable is an interface that is implemented by all comparable types |
booleans, numbers, strings, pointers, channels,
arraysof comparable types,structswhose fields are all comparable types
1 | type foo struct { |
not as the type of a variable
1 | var i comparable = 5 // cannot use type comparable outside a type constraint: interface is (or embeds) comparable |
自定义约束
Go 泛型使用
interface语法来定义约束,凡是接口类型均可作为类型参数的约束
1 | func Stringify[T fmt.Stringer](s []T) (ret []string) { |
1 | type Stringer interface { |
- 类型参数 T 的实参必须
实现fmt.Stringer 接口的所有方法 - 泛型函数的实现代码中,声明的 T 类型实例 v 仅被允许调用 fmt.Stringer 的 String 方法
只处理
非零值的元素,编译报错
1 | func Stringify[T fmt.Stringer](s []T) (ret []string) { |
通过 type embedding 扩充
约束语义,内嵌comparable
1 | package main |
增加对
排序的支持,编译报错,Go 不支持运算符重载
1 | func Stringify[T Stringer](s []T, max T) (ret []string) { |
Go 支持在
接口类型中放入类型元素(type element)信息,之前只有方法元素
1 | package main |
约束定义
type仅代表自身;~type代表以该类型为底层类型的所有类型
1 | type Ia interface { |
union elements中不能包含带有方法元素的接口类型,也不能包含预定义的约束类型,如 comparable
1 | type A interface { |
接口分类:一旦包含了
类型元素,只能用于约束
- 基本接口类型 -
无类型元素- 其
自身和其嵌入的接口类型都只包含方法元素,而不包含类型元素 - 可以当成
常规接口类型使用,也可以作为泛型类型参数的约束
- 其
- 非基本接口类型 -
有类型元素直接或者间接(type embedding)包含了类型元素的接口类型- 仅可用作
泛型类型参数的约束,或者被嵌入到其它仅作为约束的接口类型中
| 类型元素 | 用途 |
|---|---|
| 无 | 常规接口 + 约束 |
有 |
约束 |
1 | type BasicInterface interface { // 基本接口类型 |
基本接口类型仅包含方法元素- 可以基于
方法集合来确定一个类型是否实现了接口 - 判断是否可以作为
类型实参传递给约束下的类型形参
- 可以基于
非基本接口类型,既有方法元素,也有类型元素- 借助
类型集合(type set)来判断类型实参是否满足约束
- 借助
类型集合只是
重新解释了类型实现接口的判断规则
类型集合并非一个
运行时概念,因此无法通过运行时反射来查看某个接口类型的类型集合
类型集合
接口类型的
类型集合中的元素可以满足以该接口类型作为类型约束
可以将集合中的元素作为类型实参传递给该接口类型约束的类型参数
- 每个
类型都有一个类型集合 非接口类型的类型集合仅包含其自身- 用于定义
约束的接口类型的类型集合空接口类型的类型集合是一个无限集合,该集合中的元素为所有的非接口类型非空接口类型的类型集合为其定义中的接口元素的类型集合交集
接口元素:其它嵌入接口类型、方法元素、类型元素
| 接口元素 | 类型集合 |
|---|---|
| 其它嵌入接口类型 | 该嵌入接口类型的类型集合 |
| 方法元素 | 该方法的类型集合 |
| 类型元素 | 该类型元素所表示的所有类型组成的集合 如果为 ~T 形式,该集合中还包含所有以 T 为底层类型的类型 |
一个方法的类型集合为
实现了该方法的非接口类型的集合,也是一个无限集合
包含多个方法的常规接口类型的类型集合,为这些方法元素的类型集合的交集
样例
1 | type Intf1 interface { |
接口类型 I 由 4 个接口元素组成:Intf1、M1、M2、Union element(int | ~string | Intf2)
- Intf1
- Intf1 是接口类型 I 的一个嵌入接口,其自身由 3 个接口元素组成,其类型集合为这 3 个接口元素的交集
- Intf1 的类型集合
- {以 int 为底层类型的所有类型或者 string 类型,并且实现了 F1 和 F2 方法的所有类型}
- 由于不可能为 string 新增方法 F1 和 F2
- {以 int 为底层类型并且实现了 F1 和 F2 方法的所有类型}
- M1 / M2
- 方法的类型集合由所有
实现该方法的类型组成 - M1 的类型集合:{实现了 M1 的所有类型}
- M2 的类型集合:{实现了 M2 的所有类型}
- 方法的类型集合由所有
- int | ~string | Intf2
- 类型集合为 int、~string、Intf2 类型集合的
并集- int 的类型集合:int
- ~string 的类型集合:以 string 为底层类型的所有类型
- Intf2 的类型集合:以 int 或者 float64 为底层类型的所有类型
- int | ~string | Intf2 的类型集合:{以 int、string、float64 为底层类型的所有类型或者 int 类型}
- 类型集合为 int、~string、Intf2 类型集合的
I 的类型集合:{以 int 为底层类型,并且实现了 F1、F2、M1、M2 的所有类型}
1 | func foo[T I](t T) {} |
语法糖
1 | type I interface { // 独立于泛型函数外面定义 |
如果
约束对应的接口类型中仅有一个接口元素,且该元素为类型元素时,可进一步简化
1 | func bar[T ~int | ~string](t T) {} |
1 | func bar[T interface{T1 | T2 ... | Tn}](t T) |
定义
仅包含一个类型参数的泛型类型时,如果约束仅有一个*int类型元素,会报错
1 | // undefined: T |
解决方案
1 | // 完整形式 |
类型推断
1 | func DoubleDefined[S ~[]E, E ~int | ~string](s S) S { return s } |
- 在大多数情况下,可以根据
函数实参推断出类型实参 - 光靠函数实参的推断,是无法完全推断出 DoubleDefined 的所有类型实参
- DoubleDefined 的类型参数 E 并未在常规参数列表中用来声明参数
函数实参推断仅能推断出类型参数 S 的类型实参,而无法推断出 E 的类型实参
- Go 支持
约束类型推断,即基于一个已知的类型实参来推断其它类型参数的类型
函数实参 -> 类型实参 S -> 类型实参 E
















