38

Go泛型新方案 - 类型参数 | 鸟窝

 3 years ago
source link: https://colobu.com/2020/06/17/type-parameters-in-go-generic/?
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Go泛型新方案 - 类型参数

今天Ian Lance Taylor和Robert Griesemer新推出一篇文章,介绍了Go泛型的新方案。两位都是Go核心开发组中的老大,也是负责Go泛型特性的负责人。

本文带你了解Go泛型的最新进展,更详细的介绍请看The Next Step for GenericsType Parameters - Draft DesignSummary of Go Generics Discussionsissue#15292

在最新版的泛型设计中,原来的Contract概念被抛弃了。本来,额外增加一个类似接口的Contract概念有点画蛇添足,而且增加语言和代码的复杂性,所以最新版直接使用接口代替。

当然,这个草案离正式的Go泛型方案还有很大的变数,下一步是Go语言改变的提案,顺利的话,Go泛型的支持会在明年的8月份的1.17版本中推出。

当然, Go开发组已经提供了一个在线编译运行当前泛型方案的工具,你可以编写泛型代码尝鲜 :

package main
import (
"fmt"
func Print(type T)(s []T) {
for _, v := range s {
fmt.Print(v)
func main() {
Print([]string{"Hello, ", "playground\n"})

如果你接触过泛型,你会很容易理解Go泛型的概念:

  • 函数可以有一个额外的泛型参数列表,使用type关键字定义,比如func F(type T)(p T) { ... }
  • 这些类型参数(type parameter)可以用在函数参数和方法体中
  • Go类型也可以有类型参数列表,比如type M(type T) []T
  • 每一个类型参数可以有一个额外的类型约束: func F(type T Constraint)(p T) { ... }
  • 类型约束是接口类型
  • 用作类型约束的接口有一个预先声明的类型列表;如果类型或者它的底层类型是这个列表中的类型之一,那么它们才算实现了这个接口 (对接口的定义进行了扩展)
  • 使用泛型函数或者泛型类型的时候,需要传入类型参数
  • 在通常情况下,类型推断允许省略类型参数(type argument)
  • 如果类型参数有类型约束,那么它的类型参数(type argument)必须要实现这个接口
  • 泛型函数只能使用类型约束所定义的那些操作

这就是当前Go泛型的主要内容。如果你已经接触过其他语言的泛型,就比较好理解Go的泛型的概念,而且会感觉它很简单,因为很多复杂的概念都省略了(下面内容比较难以理解):

  • No specialization.不像Rust可以针对特定的类型参数进行特化
  • No metaprogramming. 没有元数据的支持(反射package不会因此改变)
  • No higher level abstraction. 不调用或者初始化函数就无从谈起带类型参数的函数
  • No general type description. 只通过类型约束(接口)来约束类型参数的行为
  • 没有协变和逆变. 简化了泛型的概念。
  • 没有操作符方法.
  • 不支持currying. 使用泛型函数和类型时,必须全部指定所有的类型参数。
  • 不支持变长类型参数.
  • 不支持适配器
  • 对非类型值(如常量)不支持参数化.

类型参数的定义使用type,而且使用小括号而不是尖括号的方式:

// Print prints the elements of any slice.
// Print has a type parameter T, and has a single (non-type)
// parameter s which is a slice of that type parameter.
func Print(type T)(s []T) {
// same as above

我个人强烈反对这个小括号的方式,因为它很容易和函数的定义弄混,本来你在定义一个数据类型的,第一眼瞄过去很容易看成是一个函数定义。对于函数来说,两个连续的小括号也很容易让人视觉疲劳。为什么不采用Java/C++泛型语法结构呢,比如F<T>,理由比较勉强,是为了照顾Go的parser,避免go parser在解析尖括号的时候避免和操作符混淆,但是人类发明工具不就是为了简化人们的操作,让工具去实现枯燥的逻辑么?同样不采用F[T]的形式也是为了避免和数组混淆。

下面是一个使用类型约束的例子:

type Stringer interface {
String() string
func Stringify(type T Stringer)(s []T) (ret []string) {
for _, v := range s {
ret = append(ret, v.String())
return ret

多类型参数的例子:

func Print2(type T1, T2)(s1 []T1, s2 []T2) { ... }

泛型类型而不是函数,不要和函数定义混淆了:

type Vector(type T) []T

不支持类型的方法中使用类型参数,这一点和其它语言不相同,但是它可以使用类型定义中的类型参数。

接口的定义扩展了,支持显式地定义实现的类型:

type SignedInteger interface {
type int, int8, int16, int32, int64

SignedInteger约束指定了类型参数必须是列表中的几种类型,或者底层结果是这几种类型的类型。当使用SignedInteger约束类型参数的时候,我们可以使用这些类型共同的操作符,比如><等。

comparable是一个预定义的约束,可以使用==!=比较符。

类型推断可以简化泛型的调用,比如Print(int)([]int{1, 2, 3})就可以省略类型参数Print([]int{1, 2, 3})

然后我们欣赏一个例子做结尾。虽然Go的泛型方案做了很多简化,但是依然会给Go编程开发带来很大的变化,目前可以这依然还是一个草稿,等到提案文档发布的时候,Go泛型的特性才算基本定型,对于绝大部分的人来说,目前只需保持关注即可。

// Package list provides a linked list of any type.
package list
// List is a linked list.
type List(type T) struct {
head, tail *element(T)
// An element is an entry in a linked list.
type element(type T) struct {
next *element(T)
// Push pushes an element to the end of the list.
func (lst *List(T)) Push(v T) {
if lst.tail == nil {
lst.head = &element(T){val: v}
lst.tail = lst.head
} else {
lst.tail.next = &element(T){val: v }
lst.tail = lst.tail.next
// Iterator ranges over a list.
type Iterator(type T) struct {
next **element(T)
// Range returns an Iterator starting at the head of the list.
func (lst *List(T)) Range() *Iterator(T) {
return Iterator(T){next: &lst.head}
// Next advances the iterator.
// It reports whether there are more elements.
func (it *Iterator(T)) Next() bool {
if *it.next == nil {
return false
it.next = &(*it.next).next
return true
// Val returns the value of the current element.
// The bool result reports whether the value is valid.
func (it *Iterator(T)) Val() (T, bool) {
if *it.next == nil {
var zero T
return zero, false
return (*it.next).val, true
// Transform runs a transform function on a list returning a new list.
func Transform(type T1, T2)(lst *List(T1), f func(T1) T2) *List(T2) {
ret := &List(T2){}
it := lst.Range()
if v, ok := it.Val(); ok {
ret.Push(f(v))
if !it.Next() {
break
return ret
0 条评论
Error: Network Error

来做第一个留言的人吧!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK