我正在参加「启航计划」

泛型的”宿世”

Q: 为什么需要泛型,不是现已有interface{}?

A: 触摸过c++、java等静态语言的童鞋关于泛型这个东西都有一定了解,泛型关于咱们进步代码复用性有极大作用。泛型在静态语言中提供了特别的数据类型做参数的承受或返回,避免了相同事务代码需要写多个不同类型的函数(办法),导致项目中出现大量的重复代码。

A:Go1.18引进前通常运用interface{}代替,下面编写个比如:

// Sum calculate the value of two value with the same data type.
//    	the data type can be int, float64, string
// a the first value
// b the second value
func Sum(a,b interface{}) interface{} {
	if reflect.TypeOf(a).Kind() != reflect.TypeOf(b).Kind() {
        return nil
    }
    switch reflect.TypeOf(a).Kind() {
	case reflect.Int:
		return reflect.ValueOf(a).Int() + reflect.ValueOf(b).Int()
	case reflect.Float64:
		return reflect.ValueOf(a).Float() + reflect.ValueOf(b).Float()
	case reflect.String:
		return reflect.ValueOf(a).String() + reflect.ValueOf(b).String()
	default:
		return nil
	}
}
func main() {
	a :=Sum(1,2)
	b := Sum(1.1,2.1)
	c := Sum("1","2")
	fmt.Printf("a:%v\t b:%v c:%v\n", a, b, c)
}

为什么要引证泛型,相信看完上述比如的小伙伴现已能够有所理解;inteface{}虽然处理了不同数据类型的参数承受和返回,可是需要反射出对应的数据类型进行分类处理,频繁地进行类型转化,这就形成代码的复杂性和性能低效。下面咱们首要根据go 泛型提案的内容进一步概述,英文阅览才能好的童鞋能够直接看该文档。go.googlesource.com/proposal/+/…

Q: 泛型的引进给go带来哪些新特性?

A: Go 1.18.0 规范库正式引进泛型支撑,其新特性如下:

  1. 参数类型(Type parameters)
  2. 束缚类型(constraints)
  3. 参数类型列表
  4. 泛型类型(Generic types)
  5. 泛型函数
  6. 泛型接收器(receiver)

golang 泛型 “此生”

Golang泛型几个新特性:

1.泛型语法(Generics syntax)

结构体语法:

type container[T any] struct{
    elem T
}

函数语法:

// 第一种
func Functionname[T any](p T) {
	...
}
// 第二种
func func1[T string | int | float64 ] (a T){
}
type Float interface {
	~float32 | ~float64
}
type Signed interface {
	~int | ~int8 | ~int16 | ~int32 | ~int64 
}
type Unsigned interface {
	~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 
}
type Integer interface {
	Signed | Unsigned
}
type Ordered interface {
	Integer | Float | ~string
}
// 第三种
func func1[T Ordered] (a T){
	fmt.Printf("current value is : %v\n", reflect.TypeOf(a))
}

2. 类型束缚(Type constraints)

golang引进了标识符 any , 可用来替代interface{},在泛型运用中,常用于表明任何类型;

func Filter[T any](s []T)  {
	for _, v := range s {
		_, _ = fmt.Printf("%v\t", v)
	}
	_, _ = fmt.Print("\n")
}
func main() {
    assembleSlice([]int{1,2,3,4})
	assembleSlice([]string{"chengdu", "sichuan"})
}

slice:

func ForEach[T any](s []T, f func(ele T, i int, s []T)) {
	for i, ele := range s {
		f(ele, i, s)
	}
}
func main() {
	s := []int{1, 2, 3, 4, 5}
	ForEach(s, func(ele int, i int, s []int) {
		fmt.Printf("ele at %d is %d\n", i, ele)
	})
}

map:

// keys return the key of a map
// here m is generic using K and V
// V is contraint using any
// K is restrained using comparable i.e any type that supports != and == operation
func keys[K comparable, V any](m map[K]V) []K {
// creating a slice of type K with length of map
    key := make([]K, len(m))
    i := 0
    for k, _ := range m {
        key[i] = k
        i++
    }
    return key
}

3.类型参数(Type parameters)

golang 泛型的“前世今生”
在这里咱们以上图为例,扼要阐述下几个概念:

类型形参 (Type parameter): T

类型束缚 (Type constraint): any 也能够运用 int | float32 | float64 等详细确认的类型表明;

类型形参列表(type parameter list) : T int | float32 | float64

泛型类型(Generic type): 类型界说中带 类型形参类型

类型实参(Type argument): 详细的类型的参数

实例化(Instantiations): 传入类型实参确认详细类型的操作

“纸上谈来终觉浅,绝知此事要躬行指”,下面就经过一个比如来具象化上面的概念

type Slice[T int | float32 | float64 | string] []T
function main() {
    var a Slice[int] = []int{1, 2, 3}
	fmt.Printf("slice: %v\n", a) // console slice: [1,2,3]
    var b Slice[string] = []string{"thank", "you", "very", "much"}
    fmt.Printf("slice: %v\n", b)
    // var c Slice[T] = []int{1.1, 2.1, 3.1}
}

这里 Slice[T] 泛型被类型实参 int 实例化为详细的类型 Slice[int]T类型形参int | float32 | float64 | stringT类型束缚Slice[T int | float32 | float64 | string]泛型类型;变量 aint类型实参

4. 其他的泛型类型

type Float interface {
	~float32 | ~float64
}
type Signed interface {
	~int | ~int8 | ~int16 | ~int32 | ~int64 
}
type Unsigned interface {
	~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 
}
type Integer interface {
	Signed | Unsigned
}
// 泛型接口
type Ordered interface {
    Integer | Float |~string
}
// 泛型结构体
type CustStruct[T int | float | string] struct {
    Code   int
    Message string
    Data   T
}
//  泛型通道
type CustChan[T int | string] chan T

在这里讲一个小知识点, 代表了指定底层类型的所有类型。Go新增该标识符就会为处理自界说类型无法实例化的问题,例如:

type Int interface {
    int | int8 | int16 | int32 | int64
}
type Slice[T Int] []T
var s Slice[int]   // ✔
type CusInt int
var s1 Slice[CusInt] // ✘ 编译过错 缺失int类型束缚,主张用~int

当然~也有限制运用,后面的语法过错内会详细解说。

当然本次1.18 还引进规范库的泛型改造,即 constraints规范包。以及内置泛型限制,匹配所有答应相等比较的类型comparable的新特性带给规范库的影响,这里我不再过多的展开讨论,详细能够参阅

taoshu.in/go/no-chang…

go.dev/blog/intro-…

Golang泛型完成原理:

执行命令 go tool compile -S -N -l sliceGeneric.go 或许 go build -gcflags=”-l -S” sliceGeneric.go > sliceGeneric.s 2>&1 编译成汇编代码,如下

FUNCDATA	$2, main.main.stkobj(SB)
LEAQ	main..dict.func1[string](SB), AX
LEAQ	go.string."a"(SB), BX
MOVL	$1, CX
PCDATA	$1, $0
CALL	main.func1[go.shape.string_0](SB)

经过汇编代码, 咱们知道GO泛型是根据编译器完成,泛型的函数/办法运用了字典来寄存,这样避免了泛型函数/办法每一次调用创立不同的函数实例,附带不同类型的参数,该字典提供了关于类型参数的相关信息,答应单个函数实例对许多不同的类型参数正确运转。为了削减性能的损耗,提出了GC Shape Stenciling 计划。gcshape是类型的调集,当被指定为类型参数之一时,这些类型能够在泛型的完成中共享通用函数/办法的相同实例。关于具有单一类型参数的泛型类型的办法,只需对gcshape相同的类型参数进行一次实例化,终究转化为对应详细的类型。

Golang泛型运用指南:

泛型切片(Generic slice)

type Slice [T int|float64|string] []T

泛型map:

type GenericMap[K int | string, V float32 | float64] map[K]V
var m GenericMap[string, float32] = map[string]float32 {
    "golang": 1.19,
    "java": 1.18,
}

泛型struct:

type Response [T string | int | float64 | bool] {
    Code int
    Msg string
    Data T
}

留意:匿名结构体是不支撑泛型的,例如:

resp := struct[T int|string] {
    Code int
    Msg  string
    Data T
}[int] {
    "error",
    2,
    3,
}
fmt.Println("response:", resp) // ✘ 编译不经过,语法过错

泛型channel:

type CusChan[T any] chan T
func main() {
    // 声明string类型带缓冲的channel
    ch := make(CusChan[string], 5)
	ch <- "hello gopher"
	x := <- ch
	close(ch)
	fmt.Printf("%v\n",x)
}

泛型函数:(generic function)

func Sum[T int|float64](a,b T) T {
  return a + b
}

自界说束缚类型

type cusInteger interface {
    int | int8 | int16 | int32 | int64
}
type cusNumb interface {
    int | float64
}
type cusFloat interface {
    float32 | float64
}
type DefinedNumb interface {
    cusInteger
    cusNumb
}
type DefinedNumb1 interface {
    cusInteger
    cusFloat
}
func ForEach[T DefinedNumb1](s []T) {
    for i,v := range s {
        fmt.Printf("the index: %v\t value: %v\n", i, v)
    }
}

留意:自界说束缚类型的交集不能为空

泛型办法:(receiver type)

在这里办法是指接收者(receiver)类型变量的函数。因此咱们还是用比如阐明这些概念:

type cusStr string
funcs cusStraddString(p string) string {
    s.append(p)
    fmt.Println(s)
}

接收器泛型 + 参数泛型

type Number interface {
	~int | ~float32 | ~float64 | ~string
}
type CusSlice[T Number] []T
// 接收器泛型
func (c CusSlice[T]) add() T {
	var sum T 
	for _, v := range c {
		sum += v
	}
	return sum
}
type CusSlice1 []int
// func (c CusSlice1) add[T int](a T) []T { //✘ 编译不经过,办法不支撑泛型
// }
// 接收器泛型 + 参数类型
func (s CusSlice[T]) add(a T) []T{
	s = append(s, a)
	return s
}
func main() {
    s := CusSlice[int] {1, 3, 4, 2, 1}
    sum := s.add()
	s = s.add(5);
	fmt.Printf("%v\n",s) // [1 3 4 2 1 5]
    fmt.Printf("sum:%v\n", sum) // sum: 11
}

泛型接口:

在Go1.18之前,常常将接口界说为一个办法集。

An interface type specifies a method set called its interface

在1.18之后,接口由办法集变为了 类型集,类型集即类型的调集。

1.根本接口 (即只要办法)

type error interface {
	Error() string
}
// 根本泛型接口
type Cus[T int| string] interface {
    Error() T
}
func Error() string {
    return "error"
}

2.一般接口(既有办法,又有类型)

type Reader interface {
    ~int | ~string | ~float32 | ~float64
	Read(p []byte) (n int, err error)
}
// 一般泛型接口
type CusInterface [T int| string] interface {
    int | string
    SetName(d T) T
    GetName() T
}
// 实例化(有必要完成Read和底层类型的类型)
func SetName(d string) string {
    return d
}
func GetName() string {}
// var c CusInterface[string] = {} 	// ✘ 编译不经过,一般泛型接口只能作为一个类型束缚
//cannot use type CusInterface[string] outside a type constraint: interface contains type 

留意:一般泛型接口,只能被作为类型参数来运用,无法被实例化

语法运用过错集锦:

~限制运用:

type CusInt int
type CusType interface {
    ~CusInt  // ✘ 只能为根本类型
    ~error   // ✘ 不能为接口
}

接口泛型限制

// 运用 | (union)衔接多个类型时不能运用包括类型有交集的类型
type CusInt int
type CusInteger interface {
    ~int | CusInt      // ✘ 
    ~int | interface{CusInt} // ✔ 接口包括的类型在外
    ~interface{int} | CusInt // ✔ 
}
// 接口中包括的类型中出现空集, 使得该代码无任何意义
type Number interface {
    int
    float32    // 虽然不会报错,可是这里现已是空集,没有任何意义,尽量不要运用该写法
}
// 一般泛型接口当包换束缚类型时,只能作类型参数运用【借用之前的比如】
type CusInterface [T int| string] interface {
    int | string
    SetName(d T) T
    GetName() T
}
func SetName(d string) string {
    return d
}
func main() {
    var c CusInterface[string] = {} 	// ✘ 编译不经过
}

语法歧义导致过错

type Reader interface {
	Read(p []byte) (n int, err error)
}
type Writer interface {
	Write(p []byte) (n int, err error)
}
type CusRead[T *Reader] []T   // ✘ 编译不经过,编译器识别为表达式,而非指针
type CusRead[T interface{*Reader}] []T // ✔ 引荐写法
type CusIo[T *Reader | *Writer] []T   // ✘ 编译不经过
type CusIo[T interface{*Reader | *Writer}] []T // ✔ 引荐写法

针对语法歧义的情景,通常咱们主张运用 interface{}

匿名方式不支撑泛型

// 匿名函数
sum := func[T int|float32|float64|string] (a, b T) T {   // ✘ 编译不经过
    return a +b;
}
fmt.Println(sum(1, 2))
// 匿名结构体
resp := struct [T int|string|bool]{  // ✘ 编译不经过
		code int
		msg string
		data T
	}[bool]{
		code: 1,
		msg: "success",
		data: true,
	}