编辑
2024-09-11
Know Go Generics
00
请注意,本文编写于 71 天前,最后修改于 71 天前,其中某些信息可能已经过时。

目录

方法集
命名类型
类型联合
类型集
复合类型字面量
约束 vs 接口
约束不是类
命名类型的限制
类型近似
类型交集
constraints 包
算术
大小比较
多个类型参数
相等比较
找最大值
抽象类型及其零值
约束字面量
省略 interface
自引用约束

[T any]上其实没有任何约束,其效果等同于interface{}。虽然T可以是任何类型,但是由于any不能反应出任何特征,所以在其上的操作非常受限,比如不能进行如下操作:

go
func Add[T any](x, y T) T { return x + y // ^ // invalid operation: operator + not defined on x (variable of type T constrained by any) }

方法集

在 Go 中定义约束就是定义接口。下面的例子使用了标准库fmt.Stringer接口约束类型参数。

go
// packge fmt type Stringer interface { String() string } // 约束 func Stringify[T fmt.Stringer](s T) string { return s.String() }

命名类型

在泛型出来以前,interface 中只能包含方法。泛型出来后 interface 被用于约束类型参数,并且其不仅可以包含方法,还可以包含其他类型,下面的例子展示了 interface 包含 int 类型:

go
type OnlyInt interface { int } // 使用 func Double[T OnlyInt](v T) T { return v * 2 }

类型联合

通过类型联合来扩大约束范围,可以看成or操作,可以包含 interface 或者其他约束。

go
type Integer interface { int | int8 | int16 | int32 | int64 } type Float interface { float32 | float64 } type Complex interface { complex64 | complex128 } type Number interface { Integer | Float | Complex }

类型集

满足某个约束的所有类型的集合。

复合类型字面量

在约束中使用匿名 struct:

go
type Pointish interface { struct{ X, Y int } }

注意,虽然约束中明确指出 struct 包含 X,Y,但是不能在函数中使用,参考

go
func GetX[T Pointish](p T) int { return p.X // ^ // p.X undefined (type T has no field or method X) }

约束 vs 接口

就目前来说,所有的接口都可以作为约束,但是约束不能当接口用,比如函数参数:

go
func Double(p Number) Number { // ^ ^ // cannot use type Number outside a type constraint: interface contains type constraints }

约束不是类

go
type Cow struct{ moo string } type Chicken struct{ cluck string } type Animal interface { Cow | Chicken } type Farm[T Animal] []T func Foo() { _ = Farm[Cow]{} _ = Farm[Chicken]{} _ = Farm[Animal]{} // ^ // cannot use type Animal outside a type constraint: interface contains type constraints }

报错的原因就是约束并不是类型,无法用于泛型实例化。

命名类型的限制

go
type Integer interface { int | int8 | int16 | int32 | int64 } type MyInt int func Double[T Integer](v T) T { return v * 2 } func Foo() { fmt.Println(Double(MyInt(1))) // ^ // MyInt does not satisfy Integer (possibly missing ~ for int in Integer) }

上面的代码无法编译通过,由于通过类型定义,MyInt 不是 int,所以对于有相同底层类型的类型来说,无法传递约束中未出现的具有相同底层类型的值。

类型近似

想要解决上面的问题可以使用~符号,其含义是包含当前类型及其具有相同底层类型的类型。

go
type ApproximatelyInt interface { ~int }

类型近似也可以用在复合类型字面量上:

go
type Pointish interface { ~struct{ x, y int } }

类型交集

在定义接口时每行为一个方法,其含义是,若要实现该接口则需要某类型实现该接口中的所有方法。对于约束来说,如果某类型想要满足一个约束,那么需要其满足该约束的所有要求。

对于下面的约束,没有任何类型能满足,因为不可能有一个类型同时是int && string

go
type Unpossible interface { int string }

想要解决这个问题可以使用类型近似:

go
type IntStringer interface { ~int fmt.Stringer }

constraints 包

官方的实验性包 constraints 包含了一些有用的约束:

  • Complex
  • Float
  • Integer
  • Ordered
  • Signed
  • Unsigned

算术

go
package product import "golang.org/x/exp/constraints" type Number interface { constraints.Integer | constraints.Float | constraints.Complex } func Product[T Number](x, y T) T { return x * y } package product_test import ( "product" "testing" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" ) func TestProductOfInts2And3Is6(t *testing.T) { t.Parallel() want := 6 got := product.Product(2, 3) if !cmp.Equal(want, got) { t.Error(cmp.Diff(want, got)) } }

大小比较

可以使用><>=<=

go
package main import ( "fmt" "golang.org/x/exp/constraints" ) func main() { fmt.Println(Greater("a", "b")) } func Greater[T constraints.Ordered](x, y T) T { if x > y { return x } return y } /* packge contraints type Ordered interface { Integer | Float | ~string } */

多个类型参数

go
func Identity[T, U any](x T, y U) (T, U) { return x, y }

相等比较

有些类型不能大小比较(ordered),但是可以相等比较(comparable)。

  • bool
  • struct 中的字段需要都是可相等比较的
  • complex numbers, pointers, channels, arrays, and interfaces.

由于很多类型无法预定义到约束中(比如用户自定义的 struct),所以 constraints 包中并没有 comparable 约束,而是直接把 comparable 内置为关键字,利用编译器进行检查。

go
func Equal[T comparable](x, y T) bool { return x == y }

找最大值

go
package main import ( "fmt" "golang.org/x/exp/constraints" ) func Max[E constraints.Ordered](s []E) E { var max E for _, e := range s { if e > max { max = e } } return max } func main() { s := []string{"faith", "hope", "love"} fmt.Println(Max(s)) }

抽象类型及其零值

上面的 Max 在其 body 中使用了类型参数 E,这个 E 被称为抽象类型。有时想返回其零值,可以使用如下方法:

go
var max E return max // 如果约束允许也可以使用这个方法 return E(0)

约束字面量

一些例子:

go
// 空约束 func Identity[T interface{}](v T) T {} // 包含一个方法 func Stringify[T interface{ String() string }](s T) string { return s.String() }

省略 interface

当约束中只有一个类型元素时,可以省略interface{...}

go
func Increment[T ~int](v T) T { return v + 1 } // 下面的是错误示例 func Increment[T ~int; ~float64](v T) T { // syntax error: unexpected semicolon, expecting comma or ] func Increment[T String() string](v T) T { // syntax error: unexpected (, expecting comma or ] type Intish ~int // syntax error: unexpected ~ in type declaration

自引用约束

go
func Contains[T interface{ Equal(T) bool }](s []T, v T) bool {} type Equaler interface { Equal(Equaler) bool }

本文作者:jdxj

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!