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,
arrays
of comparable types,structs
whose 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