Go1.18 泛型说明

1,703 阅读9分钟

本文希望能够说明在go1.18中的泛型的术语使用规则以及使用方法

  • 仅作为简中说明文档

  • 不会探究编译器原理

  • 有能力的建议去读golang说明书

如果你想更好的理解泛型

  • 把文章的代码都复制黏贴执行一遍

最后希望读者读完后能收获

  • 能够阅读泛型代码

  • 能够使用泛型

  • 不乱用泛型,写别人看不懂的代码

升级golang

  • 下载地址go.dev/dl/

  • 查看自己是x86-64还是arm64

$ uname -a | awk -F " " '{print $(NF-1)}'
  • 下载对应的go1.18.* stable版本Archive

  • 查看当前golang目录

$ whereis go
  • 前往golang的父级

  • 挪动下载的tar.gz到当前目录

  • 备份当前golang

$ mv go go1.16.18
  • 解压下载的tar.gz

  • 升级成功

$ go version

泛型类型

泛型定义

  • 类型参数(type parameter)

  • 类型约束(type constraint)

  • 泛型参数列表(type parameter list)

泛型是由类型参数类型约束组成

组成的部分可以叫做泛型参数列表

type intStruct struct {
     Value int
}

type floatStruct struct {
     Value float32
}

type intNfloatStruct[T int | float32] struct {
     Value T
}
  • intNfloatStruct 是一个泛型类型

  • T 是类型参数

    • 类型参数的作用几乎类似于函数参数,但它们可以将类型作为值而不是数据来保存
  • int | float32 是类型约束

    • 类型约束是一个集合

    • 它为相应的类型参数定义了一组允许的类型参数

    • 控制该类型参数的值支持的操作(+/-/!=/== etc.)

    • 所有的类型约束都是一个interface{}/any

  • [T int | float32]是类型参数列表

    • 类型参数列表看起来像一个普通的函数参数列表

    • 除了类型参数名称必须全部存在并且列表括在方括号而不是圆括号中

  • 多个类型参数
type intNfloatKV[K,V int | float32] map[K]V
type GStruct[
    T1 int | float32,
    T2 int | string,
    T3 int | uint32] struct {
    V1 T1
    V2 T2
    V3 T3
}
  • 红色: 类型参数

  • 蓝色: 类型约束

  • 绿色: 类型参数列表

泛型声明/实例化

  • 类型入参(Type argument)

  • 实例化(Instantiations)

var s1 intNfloatStruct[int]
s1 = intNfloatStruct[int]{
    Value: 123,
}
    
s2 := intNfloatStruct[float32]{
    Value: 123.456,
}
  • 实例化/变量定义: 在整个类型声明中,每个类型参数都替换为其对应的类型入参

其他的泛型类型

基础类型

  • 基础类型不能只有类型参数
type GINT [T int | int32 | int64 ] T
/*
MisplacedTypeParam occurs when a type parameter is used in a place where        // it is not permitted.
Example:
      type T[P any] P
Example:
    type T[P any] struct{ *P }
*/

slice

  • GSlice是slice泛型的定义

  • arr通过var声明变量

  • arr2通过:=声明变量

type GSlice [T int | float32] []T
var arr GSlice[int] = []int{1,2,3}
arr2 := GSlice[int]{1, 2, 3}

struct

  • intNfloatStruct是struct泛型的定义

  • s1通过var声明变量

  • s2通过:=声明变量

type intNfloatStruct[T int | float32] struct {
    Value T
}

var s1 intNfloatStruct[int]
s1 = intNfloatStruct[int]{
    Value: 123,
}
s2 := intNfloatStruct[float32]{
    Value: 123.456,
}

map

  • intNfloatKV是map泛型的定义

  • kv1通过var声明变量

  • kv2通过:=声明变量

type intNfloatKV[K,V int | float32] map[K]V
var kv1 intNfloatKV[int, float32] = map[int]float32{
    1: 1.23,
}
kv2 := intNfloatKV[int, float32]{
    1: 1.23,
}

指针

  • 指针必须套用interface{}使用

  • PointerINT & PointerIntNFloat 是指针泛型的定义

type PointerINT[T *int] []T // 错误
// NotAnExpr occurs when a type expression is used where a value expression

type PointerIntNFloat[T interface {
    *int | *int32 | *int64 |
        *float32 | *float64
}] []T
var parr PointerIntNFloat[*int]

套娃

  • 操作符~

    • 指定底层类型

    • ~定义的类型不能为接口

    • ~定义的类型必须为基本类型

type SINT int
s := intNfloatStruct[SINT]{} // 错误

type intNfloatStruct[T ~int | float32] struct {
     Value T
}
s := intNfloatStruct[SINT]{} // 正确

type TStruct[T ~int | // 正确
    ~SINT | //错误
    ~error] struct { // 错误
    Value T
}

类型参数列表内的套娃

  • 参数列表不能支持已经定义过的变量的单独再次定义
type GKV[K int | float32, V K] map[K]V // 错误
// cannot use a type parameter as constraint

type GKV[K int | float32, V []K] map[K]V // 正确
var gkv GKV[int, []int] = map[int][]int{ // 声明
     1: []int{4, 5, 6},
     2: []int{4, 5, 6},
}

type GKV[K int | float32, V []K, T V] map[K]V // 错误
// cannot use a type parameter as constraint

泛型的套娃

  • 套娃的类型可以被实例化,既可以被套娃
type GSlice[T int|string|float32|float64] []T

// ✗ 错误。泛型类型GSlice[T]的类型约束中不包含uint, uint8
type UintSlice[T uint|uint8] GSlice[T]  

// 正确。基于泛型类型GSlice[T]定义的新泛型类型
type IntNStringSlice[T int|string] GSlice[T]  
// 正确 基于IntAndStringSlice[T]套娃定义出的新泛型类型
type IntSlice[T int] IntNStringSlice[T] 


type GMap[T int|string] map[string]GSlice[T]
type GSlice2[T GSlice[int] | GSlice[string]] map[string]T

匿名结构体

  • 匿名结构体不能使用泛型
s := struct {
    Value int
}{
    Value: 1,
}

s := struct[T int|float32] {
    Value T
}{
    Value:1,
}[int] // 错误

泛型方法使用

泛型接收器(Generic receiver)

  • 老golang 类型兼容的stack
type Stack struct {
    vals []interface{}
}

func (s *Stack) Push(val interface{}) {
    s.vals = append(s.vals, val)
}

func (s *Stack) Pop() (interface{}, bool) {
    if len(s.vals) == 0 {
        return nil, false
    }
    top := s.vals[len(s.vals)-1]
    s.vals = s.vals[:len(s.vals)-1]
    return top, true
}
  • 泛型兼容的stack
type Stack[T int | float32] struct {
    vals []T
}

func (s *Stack[T]) Push(val T) {
    s.vals = append(s.vals, val)
}

func (s *Stack[T]) Pop() (T, bool) {
    if len(s.vals) == 0 {
        var zero T
        return zero, false
    }
    top := s.vals[len(s.vals)-1]
    s.vals = s.vals[:len(s.vals)-1]
    return top, true
}
  • 实例化类型后即可使用
var s Stack[int]
s.Push(10)
s.Push(20)
s.Push(30)
v, ok := s.Pop()
fmt.Println(v, ok)
  • receiver必须定义同样数量的类型参数
type Stack[T int | float32, V []T] struct {
    vals V
}


func (s *Stack[T]) Push(val T) {
    s.vals = append(s.vals, val)
} // 错误 没有足够量的类型参数

func (s *Stack[T, V]) Push(val V) {
    s.vals = append(s.vals, val...)
}

  • receiver的参数名字不一定要和定义一致
type Stack[T int | float32] struct {
    vals []T
}

func (s *Stack[X]) Push(val X) {
    s.vals = append(s.vals, val)
}
  • 不可以在switch使用: 类型已经被固定,所以不能被使用
func (s *Stack[T]) Do(val T) {
    // 错误
    // cannot use type switch on type parameter value val 
    // (variable of type T constrained by int|float32)
    switch val.(type) {
    case int:
        //
    }
} 

泛型函数(Generic function)

  • 泛型函数的定义

如果函数声明指定类型参数,则函数名表示泛型函数

func Add[T int | float32](a T, b T) T {
    return a + b
}
  • 泛型函数的实例化
Add[int](1,2) // 返回为int(3)
Add[float32](1.0, 2.0) // 返回为float32(3.0)

Add[int32](1,2) // 错误
  • 泛型函数支持自动实例化

    • 不使用参数变量的时候golang支持自动推导
Add(1,2) // 返回为int(3)
Add(1.0, 2.0) // 返回为float32(3.0)
  • 泛型函数支持赋值声明
AddInt := Add[int]
AddInt(1,2) // 返回为int(3)
AddInt(1.0, 2.0) // 错误
  • 泛型函数支持匿名方法
Add := func [T int | float32 ](a T, b T) T {
    return a + b
}// 错误
  • 泛型函数支持接收器

接收器的方法不接受类型参数列表

type S struct {
}

 func (s *S) Add[T int | float32](a T, b T) T {
    return a + b
}

集合和接口

类型集合

  • 使用interface对聚合任意一类的类型

    • 解决过长的类型参数列表
type intType interface {
    ~int | ~int8 | ~int16 | ~int32 | ~int64
}

type intStack[T intType] struct {
    vals []T
}
  • 类型集合只能被使用在类型参数列表中
var i intType // 错误
  • 类型集合支持取交集

    • 自己定义的interface之间可以取交集
type floatType interface {
    ~float32 | ~float64
}

type intNfloatType interface{
    intType | floatType
}

type intNfloatStack[T intType | floatType] struct {
    vals []T
}
  • 类型集合支持取并集

    • **不加 | **即使并集

    • AllIntIUint 的并集是所有的uint

type AllInt interface {
    ~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint32
}

type UintNFloat interface {
    ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | float32 | float64
}

type AllIntIUint interface { // AllInt 和 Uint的并集
    AllInt
    UintNFloat
}
  • 空集

    • 没有任何类型属于空集

    • 没有任何意义

type Empty interface {
    int
    string 
}
  • interface{} 和 any

    • interface{}是所有非接口的类型的集合

    • 在类型参数中使用interface{}等价于定义了指定了一个包含所有类型的类型集

    • any等价于interface

type Stack[T any] struct {
    vals []T
}

var s1 Stack[int]    // 正确
var s2 Stack[map[string]string]  // 正确
var s3 Stack[chan int]  // 正确
var s4 Stack[interface{}]  // 正确
  • any的定义

image

接口

基础接口(Basic interface)

  • 只有方法的interface
type File interface {
        Read([]byte) (int, error)
        Write([]byte) (int, error)
        Close() error
}
  • 基础接口的使用不在本文档说明

通用接口(General interface)

  • 类型集合是通用接口的子集

  • 有类型的interface则是通用接口

    • 类型集合层面的解释: Printer是一个int和float32的类型集合,必须在泛型列表中使用

    • 方法集合层面的解释: 如果实现了Print方法,则属于Printer集合

    • 综合的解释:

      • 底层类型为int和float32的类型且需要实现Print方法,则实现了Printer

      • Printer必须在泛型列表中实现

type Printer interface {
    ~int | ~float32
    Print()
}
  • 实现

    • 类型SINT属于Printer:

      • SINT底层类型为int,且实现了方法Print

    • 类型Stack不属于Printer:

      • Stack底层类型为struct,即便实现了Print,也不属于Printer
type SINT int // 类型SINT属于Printer
func (s SINT) Print() {
    fmt.Println(s)
}

type Stack struct { // 类型Stack不属于Printer
    val []int
}

func (s Stack) Print() {
    fmt.Println(s.val)
}
  • 使用

    • Printer只能被使用在类型参数列表
func Do[P Printer](val P) {
    val.Print()
}

s := SINT(1)
Do(s) // Golang支持自动推导
Do[SINT](s) // 手动指定类型入参
  • 使类型Stack也包括到Print中
type Printer interface {
    ~int | ~float32 | Stack
    Print()
}

泛型接口(Generics interface)

  • 带有参数列表的interface是泛型接口

  • 因为类型带有类型参数列表,所以必须要实例化

type Printer[P any] interface {
    Print(val P)
}
  • 实例化:

    • 作为类型的实例化

    • 作为变量的实例化

type StringPrinterT Printer[string] // 作为类型的实例化

// 实例化后等价于
type StringPrinterV2 interface {
    Print(val string)
}

var p Printer[int] // 作为变量的实例化
  • 实现&使用

    • 作为类型的实现&使用

    • 作为变量的实现&使用

type StringPrinter struct { // 类型StringPrinter属于StringPrinterT
}

func (s StringPrinter) Print(val string) {
    fmt.Println(val)
}

func Do[P StringPrinterT](val P) { // 作为类型的使用
    val.Print("abc")
}

s := StringPrinter{} // 使用
Do(s)

var p Printer[string] = s
p.Print("456")

三种接口的混用

  • 沿述上面的定义概念,新增两条

    • Printer自身为泛型,必须实例化后使用

    • 因为是通用接口(类型集合),所以只能在类型参数列表中使用

type Printer[P any] interface {
    ~int | ~float32
    Print(val P)
}

type SINT int // 类型SINT属于Printer
func (s SINT) Print(val int) {
    fmt.Println(val + int(s))
}

type intPrinter Printer[int]

func Do[P intPrinter](val P) {
    val.Print(100)
}

s := SINT(100)
Do(s)

var p Printer[int] = s

总结

golang1.18引入了以下概念

  • 类型参数(type parameter)

  • 类型约束(type constraint)

  • 泛型参数列表(type parameter list)

  • 类型入参(Type argument)

  • 实例化(Instantiations)

  • 泛型接收器(Generic receiver)

  • 泛型函数(Generic function)

  • 基础接口(Basic interface)

  • 通用接口(General interface)

  • 泛型接口(Generics interface)

笔者总结:

  1. 因为引入了很多概念,同时引入了很多很多的规则,增加了语言的复杂度,但是完全向下兼容,高版本golang可以兼容低版本

  2. 能使用的场景并没有C++广泛,不能支持类型/方法重载限制了泛型的想象空间

  3. 在针对不同类型写相同逻辑非常适合泛型: stack/queue/add/sub/sort etc.

题外话

  • 看完希望能点个评分

欢迎到文档 评论或在byteTech上评论探讨

参考

go.dev/ref/spec