欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Go泛型之泛型約束示例詳解

 更新時間:2023年12月29日 11:15:00   作者:賈維斯Echo  
這篇文章主要給大家介紹了關(guān)于Go泛型之泛型約束的相關(guān)資料,泛型是靜態(tài)語言中的一種編程方式,這種編程方式可以讓算法不再依賴于某個具體的數(shù)據(jù)類型,而是通過將數(shù)據(jù)類型進行參數(shù)化,以達到算法可復用的目的,需要的朋友可以參考下

一、引入

雖然泛型是開發(fā)人員表達“通用代碼”的一種重要方式,但這并不意味著所有泛型代碼對所有類型都適用。更多的時候,我們需要對泛型函數(shù)的類型參數(shù)以及泛型函數(shù)中的實現(xiàn)代碼設(shè)置限制。泛型函數(shù)調(diào)用者只能傳遞滿足限制條件的類型實參,泛型函數(shù)內(nèi)部也只能以類型參數(shù)允許的方式使用這些類型實參值。在 Go 泛型語法中,我們使用類型參數(shù)約束(type parameter constraint)(以下簡稱約束)來表達這種限制條件。

約束之于類型參數(shù)就好比函數(shù)參數(shù)列表中的類型之于參數(shù):

函數(shù)普通參數(shù)在函數(shù)實現(xiàn)代碼中可以表現(xiàn)出來的性質(zhì)與可以參與的運算由參數(shù)類型限制,而泛型函數(shù)的類型參數(shù)就由約束(constraint)來限制。

2018 年 8 月由伊恩·泰勒和羅伯特·格瑞史莫主寫的 Go 泛型第一版設(shè)計方案中,Go 引入了 contract 關(guān)鍵字來定義泛型類型參數(shù)的約束。但經(jīng)過約兩年的 Go 社區(qū)公示和討論,在 2020 年 6 月末發(fā)布的泛型新設(shè)計方案中,Go 團隊又放棄了新引入的 contract 關(guān)鍵字,轉(zhuǎn)而采用已有的 interface 類型來替代 contract 定義約束。這一改變得到了 Go 社區(qū)的大力支持。使用 interface 類型作為約束的定義方法能夠最大程度地復用已有語法,并抑制語言引入泛型后的復雜度。

但原有的 interface 語法尚不能滿足定義約束的要求。所以,在 Go 泛型版本中,interface 語法也得到了一些擴展,也正是這些擴展給那些剛剛?cè)腴T Go 泛型的 Go 開發(fā)者帶來了一絲困惑,這也是約束被認為是 Go 泛型的一個難點的原因。

下面我們來看一下 Go 類型參數(shù)的約束, Go 原生內(nèi)置的約束、如何定義自己的約束、新引入的類型集合概念等。我們先來看一下 Go 語言的內(nèi)置約束,從 Go 泛型中最寬松的約束:any 開始。

二、最寬松的約束:any

無論是泛型函數(shù)還是泛型類型,其所有類型參數(shù)聲明中都必須顯式包含約束,即便你允許類型形參接受所有類型作為類型實參傳入也是一樣。那么我們?nèi)绾伪磉_“所有類型”這種約束呢?我們可以使用空接口類型(interface{})來作為類型參數(shù)的約束:

func Print[T interface{}](sl []T) {
    // ... ...
}

func doSomething[T1 interface{}, T2 interface{}, T3 interface{}](t1 T1, t2 T2, t3 T3) {
    // ... ...
}

不過使用 interface{} 作為約束至少有以下幾點“不足”:

如果存在多個這類約束時,泛型函數(shù)聲明部分會顯得很冗長,比如上面示例中的 doSomething 的聲明部分;interface{} 包含 {} 這樣的符號,會讓本已經(jīng)很復雜的類型參數(shù)聲明部分顯得更加復雜;和 comparableSortable、ordered 這樣的約束命名相比,interface{} 作為約束的表意不那么直接。

為此,Go 團隊在 Go 1.18 泛型落地的同時又引入了一個預定義標識符:any。any 本質(zhì)上是 interface{} 的一個類型別名:

// $GOROOT/src/builtin/buildin.go
// any is an alias for interface{} and is equivalent to interface{} in all ways.
type any = interface{}

這樣,我們在泛型類型參數(shù)聲明中就可以使用 any 替代 interface{},而上述 interface{} 作為類型參數(shù)約束的幾點“不足”也隨之被消除掉了。

any 約束的類型參數(shù)意味著可以接受所有類型作為類型實參。在函數(shù)體內(nèi),使用 any 約束的形參 T 可以用來做如下操作:

  • 聲明變量
  • 同類型賦值
  • 將變量傳給其他函數(shù)或從函數(shù)返回
  • 取變量地址
  • 轉(zhuǎn)換或賦值給 interface{} 類型變量
  • 用在類型斷言或 type switch 中
  • 作為復合類型中的元素類型
  • 傳遞給預定義的函數(shù),比如 new

下面是 any 約束的類型參數(shù)執(zhí)行這些操作的一個示例:

// any.go
func doSomething[T1, T2 any](t1 T1, t2 T2) T1 {
    var a T1        // 聲明變量
    var b T2
    a, b = t1, t2   // 同類型賦值
    _ = b

    f := func(t T1) {
    }
    f(a)            // 傳給其他函數(shù)

    p := &a         // 取變量地址
    _ = p

    var i interface{} = a  // 轉(zhuǎn)換或賦值給interface{}類型變量
    _ = i

    c := new(T1)    // 傳遞給預定義函數(shù)
    _ = c

    f(a)            // 將變量傳給其他函數(shù)

    sl := make([]T1, 0, 10) // 作為復合類型中的元素類型
    _ = sl

    j, ok := i.(T1) // 用在類型斷言中
    _ = ok
    _ = j

    switch i.(type) { // 作為type switch中的case類型
    case T1:
    case T2:
    }
    return a        // 從函數(shù)返回
}

但如果對 any 約束的類型參數(shù)進行了非上述允許的操作,比如相等性或不等性比較,那么 Go 編譯器就會報錯:

// any.go

func doSomething[T1, T2 any](t1 T1, t2 T2) T1 {
    var a T1 
    if a == t1 { // 編譯器報錯:invalid operation: a == t1 (incomparable types in type set)
    }
    
    if a != t1 { // 編譯器報錯:invalid operation: a != t1 (incomparable types in type set)
    }
    ... ...
}

所以說,如果我們想在泛型函數(shù)體內(nèi)部對類型參數(shù)聲明的變量實施相等性(==)或不等性比較(!=)操作,我們就需要更換約束,這就引出了 Go 內(nèi)置的另外一個預定義約束:comparable

三、支持比較操作的內(nèi)置約束:comparable

Go 泛型提供了預定義的約束:comparable,其定義如下:

// $GOROOT/src/builtin/buildin.go

// 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).
// The comparable interface may only be used as a type parameter constraint,
// not as the type of a variable.
type comparable interface{ comparable }

不過從上述這行源碼我們?nèi)匀粺o法直觀看到 comparable 的實現(xiàn)細節(jié),Go 編譯器會在編譯期間判斷某個類型是否實現(xiàn)了 comparable 接口。

根據(jù)其注釋說明,所有可比較的類型都實現(xiàn)了 comparable 這個接口,包括:布爾類型、數(shù)值類型、字符串類型、指針類型、channel 類型、元素類型實現(xiàn)了 comparable 的數(shù)組和成員類型均實現(xiàn)了 comparable 接口的結(jié)構(gòu)體類型。下面的例子可以讓我們直觀地看到這一點:

// comparable.go

type foo struct {
    a int
    s string
}

type bar struct {
    a  int
    sl []string
}

func doSomething[T comparable](t T) T {
    var a T
    if a == t {
    }
    
    if a != t {
    }
    return a
}   
    
func main() {
    doSomething(true)
    doSomething(3)
    doSomething(3.14)
    doSomething(3 + 4i)
    doSomething("hello")
    var p *int
    doSomething(p)
    doSomething(make(chan int))
    doSomething([3]int{1, 2, 3})
    doSomething(foo{})
    doSomething(bar{}) //  bar does not implement comparable
}

我們看到,最后一行 bar 結(jié)構(gòu)體類型因為內(nèi)含不支持比較的切片類型,被 Go 編譯器認為未實現(xiàn) comparable 接口,但除此之外的其他類型作為類型實參都滿足 comparable 約束的要求。

此外還要注意,comparable 雖然也是一個 interface,但它不能像普通 interface 類型那樣來用,比如下面代碼會導致編譯器報錯:

var i comparable = 5 // 編譯器錯誤:cannot use type comparable outside a type constraint: interface is (or embeds) comparable

從編譯器的錯誤提示,我們看到:comparable 只能用作修飾類型參數(shù)的約束。

四、自定義約束

我們知道,Go 泛型最終決定使用 interface 語法來定義約束。這樣一來,凡是接口類型均可作為類型參數(shù)的約束。下面是一個使用普通接口類型作為類型參數(shù)約束的示例:

// stringify.go

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

type MyString string

func (s MyString) String() string {
    return string(s)
}

func main() {
    sl := Stringify([]MyString{"I", "love", "golang"})
    fmt.Println(sl) // 輸出:[I love golang]
}

這個例子中,我們使用的是 fmt.Stringer 接口作為約束。一方面,這要求類型參數(shù) T 的實參必須實現(xiàn) fmt.Stringer 接口的所有方法;另一方面,泛型函數(shù) Stringify 的實現(xiàn)代碼中,聲明的 T 類型實例(比如 v)也僅被允許調(diào)用 fmt.StringerString 方法。

這類基于行為(方法集合)定義的約束對于習慣了 Go 接口類型的開發(fā)者來說,是相對好理解的。定義和使用起來,與下面這樣的以接口類型作為形參的普通 Go 函數(shù)相比,區(qū)別似乎不大:

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

但現(xiàn)在我想擴展一下上面 stringify.go 這個示例,將 Stringify 的語義改為只處理非零值的元素:

// stringify_without_zero.go

func StringifyWithoutZero[T fmt.Stringer](s []T) (ret []string) {
    var zero T
    for _, v := range s {
        if v == zero { // 編譯器報錯:invalid operation: v == zero (incomparable types in type set)
            continue
        }
        ret = append(ret, v.String())
    }
    return ret
}

我們看到,針對 v 的相等性判斷導致了編譯器報錯,我們需要為類型參數(shù)賦予更多的能力,比如支持相等性和不等性比較。這讓我們想起了我們剛剛學過的 Go 內(nèi)置約束 comparable,實現(xiàn) comparable 的類型,便可以支持相等性和不等性判斷操作了。

我們知道,comparable 雖然不能像普通接口類型那樣聲明變量,但它卻可以作為類型嵌入到其他接口類型中,下面我們就擴展一下上面示例:

// stringify_new_without_zero.go
type Stringer interface {
    comparable
    String() string
}

func StringifyWithoutZero[T Stringer](s []T) (ret []string) {
    var zero T
    for _, v := range s {
        if v == zero {
            continue
        }
        ret = append(ret, v.String())
    }
    return ret
}

type MyString string

func (s MyString) String() string {
    return string(s)
}

func main() {
    sl := StringifyWithoutZero([]MyString{"I", "", "love", "", "golang"}) // 輸出:[I love golang]
    fmt.Println(sl)
}

在這個示例里,我們自定義了一個 Stringer 接口類型作為約束。在該類型中,我們不僅定義了 String 方法,還嵌入了 comparable,這樣在泛型函數(shù)中,我們用 Stringer 約束的類型參數(shù)就具備了進行相等性和不等性比較的能力了!

但我們的示例演進還沒有完,現(xiàn)在相等性和不等性比較已經(jīng)不能滿足我們需求了,我們還要為之加上對排序行為的支持,并基于排序能力實現(xiàn)下面的 StringifyLessThan 泛型函數(shù):

func StringifyLessThan[T Stringer](s []T, max T) (ret []string) {
    var zero T
    for _, v := range s {
        if v == zero || v >= max {
            continue
        }
        ret = append(ret, v.String())
    }
    return ret
}

但現(xiàn)在當我們編譯上面 StringifyLessThan 函數(shù)時,我們會得到編譯器的報錯信息 invalid operation: v >= max (type parameter T is not comparable with >=)。Go 編譯器認為 Stringer 約束的類型參數(shù) T 不具備排序比較能力。

如果連排序比較性都無法支持,這將大大限制我們泛型函數(shù)的表達能力。但是 Go 又不支持運算符重載(operator overloading),不允許我們定義出下面這樣的接口類型作為類型參數(shù)的約束:

type Stringer[T any] interface {
    String() string
    comparable
  >(t T) bool
  >=(t T) bool
  <(t T) bool
  <=(t T) bool
}

那我們又該如何做呢?別擔心,Go 核心團隊顯然也想到了這一點,于是對 Go 接口類型聲明語法做了擴展,支持在接口類型中放入類型元素(type element)信息,比如下面的 ordered 接口類型:

type ordered interface {
  ~int | ~int8 | ~int16 | ~int32 | ~int64 |
  ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr |
  ~float32 | ~float64 | ~string
}

在這個接口類型的聲明中,我們沒有看到任何方法,取而代之的是一組由豎線 “|” 分隔的、帶著小尾巴 “~” 的類型列表。這個列表表示的是,以它們?yōu)榈讓宇愋停╱nderlying type)的類型都滿足 ordered 約束,都可以作為以 ordered 為約束的類型參數(shù)的類型實參,傳入泛型函數(shù)。

我們將其組合到我們聲明的 Stringer 接口中,然后應用一下我們的 StringifyLessThan 函數(shù):

type Stringer interface {
    ordered
    comparable
    String() string
}

func main() {
    sl := StringifyLessThan([]MyString{"I", "", "love", "", "golang"}, MyString("cpp")) // 輸出:[I]
    fmt.Println(sl)
}

這回編譯器沒有報錯,并且程序輸出了預期的結(jié)果。

好了,看了那么多例子,是時候正式對 Go 接口類型語法的擴展做一個說明了。下面是擴展后的接口類型定義的組成示意圖:

我們看到,新的接口類型依然可以嵌入其他接口類型,滿足組合的設(shè)計哲學;除了嵌入的其他接口類型外,其余的組成元素被稱為接口元素(interface element)。

接口元素也有兩類,一類就是常規(guī)的方法元素(method element),每個方法元素對應一個方法原型;另一類則是此次擴展新增的類型元素(type element),即在接口類型中,我們可以放入一些類型信息,就像前面的 ordered 接口那樣。

類型元素可以是單個類型,也可以是一組由豎線 “|” 連接的類型,豎線 “|” 的含義是“并”,這樣的一組類型被稱為 union element。無論是單個類型,還是 union element 中由 “|” 分隔的類型,如果類型中不帶有 “~” 符號的類型就代表其自身;而帶有 “~” 符號的類型則代表以該類型為底層類型(underlying type)的所有類型,這類帶有 “~” 的類型也被稱為 approximation element,如下面示例:

type Ia interface {
  int | string  // 僅代表int和string
}

type Ib interface {
  ~int | ~string  // 代表以int和string為底層類型的所有類型
}

下圖是類型元素的分解說明,供你參考:

不過要注意的是:union element 中不能包含帶有方法元素的接口類型,也不能包含預定義的約束類型,如 comparable。

擴展后,Go 將接口類型分成了兩類,一類是基本接口類型(basic interface type),即其自身和其嵌入的接口類型都只包含方法元素,而不包含類型元素?;窘涌陬愋筒粌H可以當做常規(guī)接口類型來用,即聲明接口類型變量、接口類型變量賦值等,還可以作為泛型類型參數(shù)的約束。

除此之外的非空接口類型都屬于非基本接口類型,即直接或間接(通過嵌入其他接口類型)包含了類型元素的接口類型。這類接口類型僅可以用作泛型類型參數(shù)的約束,或被嵌入到其他僅作為約束的接口類型中,下面的代碼就很直觀地展示了這兩種接口類型的特征:

type BasicInterface interface { // 基本接口類型
    M1()
}

type NonBasicInterface interface { // 非基本接口類型
    BasicInterface
    ~int | ~string // 包含類型元素
}

type MyString string

func (MyString) M1() {
}  
   
func foo[T NonBasicInterface](a T) { // 非基本接口類型作為約束
}  
   
func bar[T BasicInterface](a T) { // 基本接口類型作為約束
}  
   
func main() {
    var s = MyString("hello")
    var bi BasicInterface = s // 基本接口類型支持常規(guī)用法
    var nbi NonBasicInterface = s // 非基本接口不支持常規(guī)用法,導致編譯器錯誤:cannot use type NonBasicInterface outside a type constraint: interface contains type constraints
    bi.M1()
    nbi.M1()
    foo(s)
    bar(s)           
}

看到這里,你可能會覺得有問題了:基本接口類型,由于其僅包含方法元素,我們依舊可以基于之前講過的方法集合,來確定一個類型是否實現(xiàn)了接口,以及是否可以作為類型實參傳遞給約束下的類型形參。但對于只能作為約束的非基本接口類型,既有方法元素,也有類型元素,我們?nèi)绾闻袛嘁粋€類型是否滿足約束,并作為類型實參傳給類型形參呢?

這時候我們就需要 Go 泛型落地時引入的新概念:類型集合(type set),類型集合將作為后續(xù)判斷類型是否滿足約束的基本手段。

五、類型集合(type set)

類型集合(type set)的概念是 Go 核心團隊在 2021 年 4 月更新 Go 泛型設(shè)計方案時引入的。在那一次方案變更中,原方案中用于接口類型中定義類型元素的 type 關(guān)鍵字被去除了,泛型相關(guān)語法得到了進一步的簡化。

一旦確定了一個接口類型的類型集合,類型集合中的元素就可以滿足以該接口類型作為的類型約束,也就是可以將該集合中的元素作為類型實參傳遞給該接口類型約束的類型參數(shù)。

那么類型集合究竟是怎么定義的呢?下面我們來看一下。

結(jié)合 Go 泛型設(shè)計方案以及Go 語法規(guī)范,我們可以這么來理解類型集合:

  • 每個類型都有一個類型集合;
  • 非接口類型的類型的類型集合中僅包含其自身,比如非接口類型 T,它的類型集合為 {T},即集合中僅有一個元素且這唯一的元素就是它自身。

但我們最終要搞懂的是用于定義約束的接口類型的類型集合,所以以上這兩點都是在為下面接口類型的類型集合定義做鋪墊,定義如下:

  • 接口類型(any 或 interface{})的類型集合是一個無限集合,該集合中的元素為所有非接口類型。這個與我們之前的認知也是一致的,所有非接口類型都實現(xiàn)了空接口類型;
  • 非空接口類型的類型集合則是其定義中接口元素的類型集合的交集(如下圖)。

由此可見,要想確定一個接口類型的類型集合,我們需要知道其中每個接口元素的類型集合。

上面我們說過,接口元素可以是其他嵌入接口類型,可以是常規(guī)方法元素,也可以是類型元素。當接口元素為其他嵌入接口類型時,該接口元素的類型集合就為該嵌入接口類型的類型集合;而當接口元素為常規(guī)方法元素時,接口元素的類型集合就為該方法的類型集合。

到這里你可能會很疑惑:一個方法也有自己的類型集合?

是的。Go 規(guī)定一個方法的類型集合為所有實現(xiàn)了該方法的非接口類型的集合,這顯然也是一個無限集合,如下圖所示:

通過方法元素的類型集合,我們也可以合理解釋僅包含多個方法的常規(guī)接口類型的類型集合,那就是這些方法元素的類型集合的交集,即所有實現(xiàn)了這三個方法的類型所組成的集合。

最后我們再來看看類型元素。類型元素的類型集合相對來說是最好理解的,每個類型元素的類型集合就是其表示的所有類型組成的集合。如果是 ~T 形式,則集合中不僅包含 T 本身,還包含所有以 T 為底層類型的類型。如果使用 Union element,則類型集合是所有豎線 “|” 連接的類型的類型集合的并集。

接下來,我們來做個稍復雜些的實例分析,我們來分析一下下面接口類型I 的類型集合:

type Intf1 interface {
    ~int | string
  F1()
  F2()
}

type Intf2 interface {
  ~int | ~float64
}

type I interface {
    Intf1 
    M1()
    M2()
    int | ~string | Intf2
}

我們看到,接口類型 I 由四個接口元素組成,分別是 Intf1M1、M2Union element “int | ~string | Intf2”,我們只要分別求出這四個元素的類型集合,再取一個交集即可。

  • Intf1 的類型集合

Intf1 是接口類型 I 的一個嵌入接口,它自身也是由三個接口元素組成,它的類型集合為這三個接口元素的交集,即 {以 int 為底層類型的所有類型、string、實現(xiàn)了 F1 和 F2 方法的所有類型}。

  • M1 和 M2 的類型集合

就像前面所說的,方法的類型集合是由所有實現(xiàn)該方法的類型組成的,因此 M1 的方法集合為 {實現(xiàn)了 M1 的所有類型}M2 的方法集合為 {實現(xiàn)了 M2 的所有類型}

  • int | ~string | Intf2 的類型集合

這是一個類型元素,它的類型集合為 int、~stringIntf2 類型集合的并集。int 類型集合就是 {int},~string 的類型集合為 {以 string 為底層類型的所有類型},而 Intf2 的類型集合為 {以 int 為底層類型的所有類型,以 float64 為底層類型的所有類型}

為了更好地說明最終類型集合是如何取得的,我們在下面再列一下各個接口元素的類型集合:

  • Intf1 的類型集合:{以 int 為底層類型的所有類型、string、實現(xiàn)了 F1 和 F2 方法的所有類型};
  • M1 的類型集合:{實現(xiàn)了 M1 的所有類型};
  • M2 的類型集合:{實現(xiàn)了 M2 的所有類型};
  • int | ~string | Intf2 的類型集合:{以 int 為底層類型的所有類型,以 float64 為底層類型的所有類型,以 string 為底層類型的所有類型}。

接下來我們?nèi)∫幌律厦婕系慕患簿褪?{以 int 為底層類型的且實現(xiàn)了 F1、F2M1、M2 這個四個方法的所有類型}。

現(xiàn)在我們用代碼來驗證一下:

// typeset.go

func doSomething[T I](t T) {
}

type MyInt int

func (MyInt) F1() {
}
func (MyInt) F2() {
}
func (MyInt) M1() {
}
func (MyInt) M2() {
}

func main() {
    var a int = 11
    //doSomething(a) //int does not implement I (missing F1 method)

    var b = MyInt(a)
    doSomething(b) // ok
}

如上代碼,我們定義了一個以 int 為底層類型的自定義類型 MyInt 并實現(xiàn)了四個方法,這樣 MyInt 就滿足了泛型函數(shù) doSomething 中約束 I 的要求,可以作為類型實參傳遞。

六、簡化版的約束形式

在前面的介紹和示例中,泛型參數(shù)的約束都是一個完整的接口類型,要么是獨立定義在泛型函數(shù)外面(比如下面代碼中的 I 接口),要么以接口字面值的形式,直接放在類型參數(shù)列表中對類型參數(shù)進行約束,比如下面示例中 doSomething2 類型參數(shù)列表中的接口類型字面值:

type I interface { // 獨立于泛型函數(shù)外面定義
    ~int | ~string
}

func doSomething1[T I](t T)
func doSomething2[T interface{~int | ~string}](t T) // 以接口類型字面值作為約束

但在約束對應的接口類型中僅有一個接口元素,且該元素為類型元素時,Go 提供了簡化版的約束形式,我們不必將約束獨立定義為一個接口類型,比如上面的 doSomething2 可以簡寫為下面簡化形式:

func doSomething2[T ~int | ~string](t T) // 簡化版的約束形式

你看,這個簡化版的約束形式就是去掉了 interface 關(guān)鍵字和外圍的大括號,如果用一個一般形式來表述,那就是:

func doSomething[T interface {T1 | T2 | ... | Tn}](t T)

等價于下面簡化版的約束形式:

func doSomething[T T1 | T2 | ... | Tn](t T) 

這種簡化形式也可以理解為一種類型約束的語法糖。不過有一種情況要注意,那就是定義僅包含一個類型參數(shù)的泛型類型時,如果約束中僅有一個 *int 型類型元素,我們使用上述簡化版形式就會有問題,比如:

type MyStruct [T * int]struct{} // 編譯錯誤:undefined: T
                                // 編譯錯誤:int (type) is not an expression

當遇到這種情況時,Go 編譯器會將該語句理解為一個類型聲明:MyStruct 為新類型的名字,而其底層類型為 [T *int]struct{},即一個元素為空結(jié)構(gòu)體類型的數(shù)組。

那么怎么解決這個問題呢?目前有兩種方案,一種是用完整形式的約束:

type MyStruct[T interface{*int}] struct{} 

另外一種則是在簡化版約束的 *int 類型后面加上一個逗號:

type MyStruct[T *int,] struct{} 

七、約束的類型推斷

在大多數(shù)情況下,我們都可以使用類型推斷避免在調(diào)用泛型函數(shù)時顯式傳入類型實參,Go 泛型可以根據(jù)泛型函數(shù)的實參推斷出類型實參。但當我們遇到下面示例中的泛型函數(shù)時,光依靠函數(shù)類型實參的推斷是無法完全推斷出所有類型實參的:

func DoubleDefined[S ~[]E, E constraints.Integer](s S) S {

因為像 DoubleDefined 這樣的泛型函數(shù),其類型參數(shù) E 在其常規(guī)參數(shù)列表中并未被用來聲明輸入?yún)?shù),函數(shù)類型實參推斷僅能根據(jù)傳入的 S 的類型,推斷出類型參數(shù) S 的類型實參,E 是無法推斷出來的。所以為了進一步避免開發(fā)者顯式傳入類型實參,Go 泛型支持了約束類型推斷(constraint type inference),即基于一個已知的類型實參(已經(jīng)由函數(shù)類型實參推斷判斷出來了),來推斷其他類型參數(shù)的類型。

我們還以上面 DoubleDefined 這個泛型函數(shù)為例,當通過實參推斷得到類型 S 后,Go 會嘗試啟動約束類型推斷來推斷類型參數(shù) E 的類型。但你可能也看出來了,約束類型推斷可成功應用的前提是 S 是由 E 所表示的。

八、小結(jié)

本文我們先從 Go 泛型內(nèi)置的約束 anycomparable 入手,充分了解了約束對于泛型函數(shù)的類型參數(shù)以及泛型函數(shù)中的實現(xiàn)代碼的限制與影響。然后,我們了解了如何自定義約束,知道了因為 Go 不支持操作符重載,單純依賴基于行為的接口類型(僅包含方法元素)作約束是無法滿足泛型函數(shù)的要求的。這樣我們進一步學習了 Go 接口類型的擴展語法:支持類型元素。

既有方法元素,也有類型元素,對于作為約束的非基本接口類型,我們就不能像以前那樣僅憑是否實現(xiàn)方法集合來判斷是否實現(xiàn)了該接口,新的判定手段為類型集合。并且,類型集合不是一個運行時概念,我們目前還無法通過運行時反射直觀看到一個接口類型的類型集合是什么!

Go 內(nèi)置了像 any、comparable 的約束,后續(xù)隨著 Go 核心團隊在 Go 泛型使用上的經(jīng)驗的逐漸豐富,Go 標準庫中會增加更多可直接使用的約束。原計劃在 Go 1.18 版本加入 Go 標準庫的一些泛型約束的定義暫放在了 Go 實驗倉庫中,你可以自行參考。

到此這篇關(guān)于Go泛型之泛型約束的文章就介紹到這了,更多相關(guān)Go泛型約束內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Go語言Mock使用基本指南詳解

    Go語言Mock使用基本指南詳解

    這篇文章主要介紹了Go語言Mock使用基本指南詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-06-06
  • Go函數(shù)全景從基礎(chǔ)到高階深度剖析

    Go函數(shù)全景從基礎(chǔ)到高階深度剖析

    本文深入探索Go語言中的函數(shù)特性,從基礎(chǔ)函數(shù)定義到特殊函數(shù)類型,再到高階函數(shù)的使用和函數(shù)調(diào)用優(yōu)化,通過詳細的代碼示例和專業(yè)解析,讀者不僅可以掌握函數(shù)的核心概念,還能了解如何在實踐中有效利用這些特性來提高代碼質(zhì)量和性能
    2023-10-10
  • go語言之給定英語文章統(tǒng)計單詞數(shù)量(go語言小練習)

    go語言之給定英語文章統(tǒng)計單詞數(shù)量(go語言小練習)

    這篇文章給大家分享go語言小練習給定英語文章統(tǒng)計單詞數(shù)量,實現(xiàn)思路大概是利用go語言的map類型,以每個單詞作為關(guān)鍵字存儲數(shù)量信息,本文通過實例代碼給大家介紹的非常詳細,需要的朋友參考下吧
    2020-01-01
  • Go存儲基礎(chǔ)使用direct io方法實例

    Go存儲基礎(chǔ)使用direct io方法實例

    這篇文章主要介紹了Go存儲基礎(chǔ)之如何使用direct io方法實例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-12-12
  • Golang中關(guān)于defer的盲區(qū)梳理

    Golang中關(guān)于defer的盲區(qū)梳理

    關(guān)于Go中的defer,是做什么的?執(zhí)行順序是怎么樣的?相信學過Go語言的同學,已經(jīng)不在陌生,今天就來講講其中需要掌握的幾個知識點,希望對大家有所幫助
    2023-03-03
  • 總結(jié)Golang四種不同的參數(shù)配置方式

    總結(jié)Golang四種不同的參數(shù)配置方式

    這篇文章主要介紹了總結(jié)Golang四種不同的參數(shù)配置方式,文章圍繞主題展開詳細的內(nèi)容戒殺,具有一定的參考價值,需要的小伙伴可以參考一下
    2022-09-09
  • Go語言庫系列之flag的具體使用

    Go語言庫系列之flag的具體使用

    這篇文章主要介紹了Go語言庫系列之flag的具體使用,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-04-04
  • Go語言封裝一個Cron定時任務管理器

    Go語言封裝一個Cron定時任務管理器

    在現(xiàn)代應用中,定時任務是非常常見的需求,無論是用于定時清理數(shù)據(jù),還是定時執(zhí)行系統(tǒng)維護任務,下面我們就來使用Go語言封裝一個Cron定時任務管理器吧
    2024-12-12
  • Go實現(xiàn)自動解壓縮包以及讀取docx/doc文件內(nèi)容詳解

    Go實現(xiàn)自動解壓縮包以及讀取docx/doc文件內(nèi)容詳解

    在開發(fā)過程中,我們常常需要處理壓縮包和文檔文件。本文將介紹如何使用Go語言自動解壓縮包和讀取docx/doc文件,需要的可以參考一下
    2023-03-03
  • Golang channel死鎖的幾種情況小結(jié)

    Golang channel死鎖的幾種情況小結(jié)

    本文主要介紹了Golang channel死鎖的幾種情況小結(jié),詳細的介紹了六種情況,具有一定的參考價值,感興趣的可以了解一下
    2024-08-08

最新評論