精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

Go 1.18 的那些事——工作區、模糊測試、泛型

原創 精選
開發
2022 年 3 月 15 日,Google 發布了萬眾矚目的 Golang 1.18,帶來了好幾個重大的新特性。本文將簡單講述這幾個新特性的相關內容。

作者 | 張聞闐

前言

2022 年 3 月 15 日,Google 發布了萬眾矚目的 Golang 1.18,帶來了好幾個重大的新特性,包括:

  • 解決本地同時開發多個倉庫帶來的一些問題的工作區(Workspace)
  • 能夠自動探測代碼分支,隨機生成輸入,并且檢查代碼是否會 panic 的模糊測試(Fuzzing Test)
  • 眾多開發者盼星星盼月亮終于等到的泛型支持。

本文將簡單講述這三個特性的相關內容。

Go 工作區模式(Go Workspace Mode)

現實的情況

多倉庫同時開發

在實際的開發工作中,我們經常會同時修改存在依賴關系的多個 module,例如在某個 service 模塊上實現需求的同時,也需要對項目組的某個 common 模塊做出修改,整個的工作流就會變成下面這樣:

圖片

可以看到,每次修改 Common 庫,都需要將代碼 push 到遠端,然后再修改本地 service 倉庫的依賴,再通過 go mod tidy 從遠端拉取 Common 代碼,不可謂不麻煩。

有些同學可能會問了,這種情況,在 service 倉庫的 go.mod 中添加一條 replace 不就能夠解決嗎?

但是,如果在 go.mod 中使用 replace,在維護上需要付出額外的心智成本,萬一將帶有 replace 的 go.mod 推到遠端代碼庫了,其他同學不就一臉懵逼了?

多個新倉庫開始開發

假設此時我正在開發兩個新的模塊,分別是:

code.byted.org/SomeNewProject/Common
code.byted.org/SomeNewProject/MyService

并且 MyService 依賴于 Common。

在開發過程中,出于各種原因,有可能不會立即將代碼推送到遠端,那么此時假設我需要本地編譯 MyService,就會出現 go build(或者 go mod tidy)自動下載依賴失敗,因為此時 Common 庫根本就沒有發布到代碼庫中。

出于和上述“多倉庫同時開發”相同的理由,replace 也不應該被添加到 MyService 的 go.mod 文件中。

工作區模式是什么

Go 工作區模式最早出現于 Go 開發者 Michael Matloob 在 2021 年 4 月提出的一個名為“Multi-Module Workspaces in cmd/go”的提案。

這個提案中提出,新增一個 go.work 文件,并且在這個文件中指定一系列的本地路徑,這些本地路徑下的 go module 共同構成一個工作區(workspace),go 命令可以操作這些路徑下的 go module,在編譯時也會優先使用這些 go module。

使用如下命令就可以初始化一個工作區,并且生成一個空的 go.work 文件:

go work init .

新生成的 go.work 文件內容如下:

go 1.18
directory ./.

go.work 文件中,directory 指示了工作區的各個 module 目錄,在編譯代碼時,會優先使用同一個 workspace 下的 module。

在 go.work 中,也支持使用 replace 來指定使用本地代碼庫,但在大多數情況下,更好的做法是將依賴的本地代碼庫的路徑加入 directory 中。

推薦的使用方法

因為 go.work 描述的是本地的工作區,所以也是不能提交到遠端代碼庫的,雖然可以在.gitignore 中加入這個文件,但是最推薦的做法還是在本地代碼庫的上層目錄使用 go.work。

例如上述的“多個新倉庫開始開發”的例子,假設我的兩個倉庫的本地路徑分別是:

/Users/bytedance/dev/my_new_project/common
/Users/bytedance/dev/my_new_project/my_service

那么我就可以在“/Users/bytedance/dev/my_new_project”目錄下生成一個如下內容的 go.work:

/Users/bytedance/dev/my_new_project/go.work:

go 1.18

directory (
./common
./my_service
)

在上層目錄放置 go.work,也可以將多個目錄組織成一個 workspace,并且由于上層目錄本身不受 git 管理,所以也不用去管 gitignore 之類的問題,是比較省心的方式。

使用時的注意點

目前(go 1.18)僅 go build 會對 go.work 做出判斷,而 go mod tidy 并不 care Go 工作區。

Go 模糊測試(Go Fuzzing Test)

為什么 Golang 要支持模糊測試

從 1.18 起,模糊測試(Fuzzing Test)作為語言安全的一環,加入了 Golang 的 testing 標準庫。Golang 加入模糊測試的原因非常明顯:安全是程序員在構建軟件的過程中必不可少且日益重要的考量因素。

Golang 至今為止,已經在保障語言安全方面提供了很多的特性和工具,例如強制使用顯式類型轉換、禁止隱式類型轉換、對數組與切片的越界訪問檢查、通過 go.sum 對依賴包進行哈希校驗等等。

在進入云原生時代之后,Golang 成為了云原生基礎設施與服務的頭部語言之一。這些系統對安全性的要求自然不言而喻。尤其是針對用戶的輸入,不被用戶的輸入弄出處理異常、崩潰、被操控是對這些系統的基本要求之一。

這就要求我們的系統在處理任何用戶輸入的時候都能保持穩定,但是傳統的質量保障手段,例如 Code Review、靜態分析、人工測試、Unit Test 等等,在面對日益復雜的系統時,自然就無法窮盡所有可能的輸入組合,尤其是一些非常不明顯的 corner case。

而模糊測試就是業界在解決這方面問題的優秀實踐之一,Golang 選擇支持它也就不難理解了。

模糊測試是什么

模糊測試是一種通過數據構造引擎,輔以開發者可以提供的一些初始數據,自動構造出一些隨機數據,作為對程序的輸入來進行測試的一種方式。模糊測試可以幫助開發人員發現難以發現的穩定性、邏輯性甚至是安全性方面的錯誤,特別是當被測系統變得更加復雜時。

模糊測試在具體的實現上,通常可以不依賴于開發測試人員定義好的數據集,取而代之的則是一組通過數據構造引擎自行構造的一系列隨機數據。模糊測試會將這些數據作為輸入提供給待測程序,并且監測程序是否出現 panic、斷言失敗、無限循環,或者其他什么異常情況。這些通過數據構造引擎生成的數據被稱為語料(corpus)。另外模糊測試其實也是一種持續測試的手段,因為如果不限制執行的次數或者執行的最大時間,它就會一直不停的執行下去。

Golang 的模糊測試由于被實現在了編譯器工具鏈中,所以采用了一種名為“覆蓋率引導的 fuzzing”的入參生成技術,大致運行過程如下:

圖片

Golang 的模糊測試如何使用

Golang 的模糊測試在使用時,可以簡單地直接使用,也可以自己提供一些初始的語料。

最簡單的實踐例子

模糊測試的函數也是放在 xxx_test.go 里的,編寫一個最簡單的模糊測試例子(明顯的除 0 錯誤):

package main

import "testing"
import "fmt"

func FuzzDiv(f *testing.F) {
f.Fuzz(func(t *testing.T, a, b int) {
fmt.Println(a/b)
})
}

可以看到類似于單元測試,模糊測試的函數名都是 FuzzXxx 格式,且接受一個 testing.F 指針對象。

然后在函數中使用 f.Fuzz 對指定的函數進行模糊測試,被測試的函數的第一個參數必須是“*testing.T”類型,后面可以跟任意多個基本類型的參數。

編寫完成之后,使用這樣的命令來啟動模糊測試:

go test -fuzz .

模糊測試默認會一直進行下去,只要被測試的函數不 panic 不出錯。可以通過“-fuzztime”選項來限制模糊測試的時間:

go test -fuzztime 10s -fuzz .

使用模糊測試對上述代碼進行測試時,會碰到產生 panic 的情況,此時模糊測試會輸出如下信息:

warning: starting with empty corpus
fuzz: elapsed: 0s, execs: 0 (0/sec), new interesting: 0 (total: 0)
fuzz: elapsed: 0s, execs: 1 (65/sec), new interesting: 0 (total: 0)
--- FAIL: FuzzDiv (0.02s)
--- FAIL: FuzzDiv (0.00s)
testing.go:1349: panic: runtime error: integer divide by zero
goroutine 11 [running]:
runtime/debug.Stack()
/Users/bytedance/.mytools/go/src/runtime/debug/stack.go:24 +0x90
testing.tRunner.func1()
/Users/bytedance/.mytools/go/src/testing/testing.go:1349 +0x1f2
panic({0x1196b80, 0x12e3140})
/Users/bytedance/.mytools/go/src/runtime/panic.go:838 +0x207
mydev/fuzz.FuzzDiv.func1(0x0?, 0x0?, 0x0?)
/Users/bytedance/Documents/dev_test/fuzz/main_test.go:8 +0x8c
reflect.Value.call({0x11932a0?, 0x11cbf68?, 0x13?}, {0x11be123, 0x4}, {0xc000010420, 0x3, 0x4?})
/Users/bytedance/.mytools/go/src/reflect/value.go:556 +0x845
reflect.Value.Call({0x11932a0?, 0x11cbf68?, 0x514?}, {0xc000010420, 0x3, 0x4})
/Users/bytedance/.mytools/go/src/reflect/value.go:339 +0xbf
testing.(*F).Fuzz.func1.1(0x0?)
/Users/bytedance/.mytools/go/src/testing/fuzz.go:337 +0x231
testing.tRunner(0xc000003a00, 0xc00007e3f0)
/Users/bytedance/.mytools/go/src/testing/testing.go:1439 +0x102
created by testing.(*F).Fuzz.func1
/Users/bytedance/.mytools/go/src/testing/fuzz.go:324 +0x5b8


Failing input written to testdata/fuzz/FuzzDiv/2058e4e611665fa289e5c0098bad841a6785bf79d30e47b96d8abcb0745a061c
To re-run:
go test -run=FuzzDiv/2058e4e611665fa289e5c0098bad841a6785bf79d30e47b96d8abcb0745a061c
FAIL
exit status 1
FAIL mydev/fuzz 0.059s

其中的:

Failing input written to testdata/fuzz/FuzzDiv/2058e4e611665fa289e5c0098bad841a6785bf79d30e47b96d8abcb0745a061c

這一行表示模糊測試將出現 panic 的測試入參保存到了這個文件里面,此時嘗試輸出這個文件的內容:

go test fuzz v1
int(-60)
int(0)

就可以看到引發 panic 的入參,此時我們就可以根據入參檢查我們的代碼是哪里有問題。當然,這個簡單的例子就是故意寫了個除 0 錯誤。

提供自定義語料

Golang 的模糊測試還允許開發者自行提供初始語料,初始語料可以通過“f.Add”方法提供,也可以將語料以上面的“Failing input”相同的格式,寫入“testdata/fuzz/FuzzXXX/自定義語料文件名”中。

使用時的注意點

目前 Golang 的模糊測試僅支持被測試的函數使用這些類型的參數:

[]byte, string, bool, byte, rune, float32, float64,
int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64

根據標準庫的文檔描述,更多的類型支持會在以后加入。

Go 的泛型

Golang 在 1.18 中終于加入了對泛型的支持,有了泛型之后,我們可以這樣寫一些公共庫的代碼:

舊代碼(反射):

func IsContainCommon(val interface{}, array interface{}) bool {
switch reflect.TypeOf(array).Kind() {
case reflect.Slice:
lst := reflect.ValueOf(array)
for index := 0; index < lst.Len(); index++ {
if reflect.DeepEqual(val, lst.Index(index).Interface()) {
return true
}
}
}
return false
}

新代碼(泛型):

func IsContainCommon[T any](val T, array []T) bool {
for _, item := range array {
if reflect.DeepEqual(val, item) {
return true
}
}
return false
}

泛型在 Golang 中增加了三個新的重要特性:

  1. 在定義函數和類型時,支持使用類型參數(Type parameters)
  2. 將接口(interface)重新定義為“類型的集合”
  3. 泛型支持類型推導

下面逐個對這些內容進行簡單說明。

類型參數(Type Parameters)

現在在定義函數和類型時,支持使用“類型參數”,類型參數的列表和函數參數列表很相似,只不過它使用的是方括號:

func Min[T constraints.Ordered](x, y T) T {
if x < y {
return x
}
return y
}

上述的代碼中,給 Min 函數定義了一個參數類型 T,這很類似于 C++中的“template < typename T > ”,只不過在 Golang 中,可以為這種參數類型指定它需要滿足的“約束”。在這個例子中,使用的“約束”是“constraints.Ordered”。

然后就可以按照如下方式,使用這個函數了:

x := Min[int](1, 2)
y := Min[float64](1.1, 2.2)

為泛型函數指定類型參數的過程叫做“實例化(Instantiation)”,也可以將實例化后的函數保存成為函數對象,并且進一步使用:

f := Min[int64] // 這一步保存了一個實例化的函數對象
n := f(123, 456)

同樣的,自定義的類型也支持泛型:

type TreeNode[T interface{}] struct {
left, right *TreeNode[T]
value T
}

func (t *TreeNode[T]) Find(x T) { ... }

var myBinaryTree TreeNode[int]

如上述代碼,struct 類型在使用泛型時,支持自己的成員變量和自己持有同樣的泛型類型。

類型集合(Type Sets)

下面稍微深入的講一下上述例子提到的“約束”。上文的例子中的“int”“float64”“int64”在實例化時,實際上是被作為“參數”傳遞給了“類型參數列表”,即上文例子中的“[T constraints.Ordered]”。

就像傳遞普通參數需要校驗參數的類型一樣,傳遞類型參數時也需要對被傳遞的類型參數進行校驗,檢查被傳遞的類型是否滿足要求。

例如上文例子中,使用“int”“float64”“int64”這幾個類型對 Min 函數進行實例化時,編譯器都會檢查這些參數是否滿足“constraints.Ordered”這個約束。而這個約束描述了所有可以使用“<”進行比較的類型的集合,這個約束本身也是一個 interface。

在 Go 的泛型中,類型約束必須是一種 interface,而“傳統”的 Golang 中對 interface 的定義是“一個接口定義了一組方法集合”,任何實現了這組方法集合的類型都實現了這個 interface:

圖片

不過這里就出現了一個問題:“<”的比較顯然不是一個方法(Go 當中不存在 C++的運算符重載),而描述了這個約束的 constraints.Ordered 自身的確也是一個 interface。

所以從 1.18 開始,Golang 將 Interface 重新定義為“一組類型的集合”,按照以前對 interface 的看法,也可以將一個 interface 看成是“所有實現了這個 interface 的方法集合的類型所構成的集合”:

圖片

其實兩種看法殊途同歸,但是后者顯然可以更靈活,直接將一組具體類型指定成一個 interface,即使這些類型沒有任何的方法。

例如在 1.18 中,可以這樣定義一個 interface:

type MyInterface interface {
int|bool|string
}

這樣的定義表示 int/bool/string 都可以被當作 MyInterface 進行使用。

那么回到 constraints.Ordered,它的定義實際上是:

type Ordered interface {
Integer|Float|~string
}

type Float interface {
~float32|~float64
}

type Integer interface {
Signed|Unsigned
}

type Signed interface {
~int|~int8|~int16|~int32|~int64
}

type Unsigned interface {
~uint|~uint8|~uint16|~uint32|~uint64
}

其中前置的“~”符號表示“任何底層類型是后面所跟著的類型的類型”,例如:

type MyString string

這樣定義的 MyString 是可以滿足“~string”的類型約束的。

類型推導(Type Inference)

最后,所有支持泛型的語言都會有的類型推導自然也不會缺席。類型推導功能可以允許使用者在調用泛型函數時,無需指定所有的類型參數。例如下面這個函數:

// 將F類型的slice變換為T類型的slice
// 關鍵字 any 等同于 interface{}
func Map[F, T any](src []F, f func(F) T) []T {
ret := make([]T, 0, len(src))
for _, item := range src {
ret = append(ret, f(item))
}
return ret
}

在使用時可以這樣:

var myConv := func(i int)string {return fmt.Sprint(i)}
var src []int
var dest []string
dest = Map[int, string](src, myConv) // 明確指定F和T的類型
dest = Map[int](src, myConv) // 僅指定F的類型,T的類型交由編譯器推導
dest = Map(src, myConv) // 完全不指定類型,F和T都交由編譯器推導

泛型函數在使用時,可以不指定具體的類型參數,也可以僅指定類型參數列表左邊的部分類型。當自動的類型推導失敗時,編譯器會報錯。

Golang 泛型中的類型推導主要分為兩大部分:

  1. 函數參數類型推導:通過函數的入參,對類型參數對應的具體類型進行推導。
  2. 約束類型推導:通過已知具體類型的類型參數,來推斷出未知類型參數的具體類型。

而這兩種類型推導,都依賴一種名為“類型統一化(Type Unification)”的技術。

類型統一化(Type Unification)

類型統一化是對兩個類型進行比較,這兩個類型有可能本身是一個類型參數,也有可能包含一個類型參數。

比較的過程是對這兩個類型的“結構”進行對比,并且要求被比較的兩個類型滿足下列條件:

  1. 剔除類型參數后,兩個類型的“結構”必須能夠匹配
  2. 剔除類型參數后,結構中剩余的具體類型必須相同
  3. 如果兩者均不含類型參數,那么兩者的類型必須完全相同,或者底層數據類型完全相同

這里說的“結構”,指的是類型定義中的 slice、map、function 等等,以及它們之間的任意嵌套。

滿足這幾個條件時,類型統一性對比才算做成功,編譯器才能進一步對類型參數進行推測,例如:

如果我們此時有“T1”、“T2”兩個類型參數,那么“[]map[int]bool”可以匹配如下類型:

[]map[int]bool // 它本身
T1 // T1被推斷為 []map[int]bool
[]T1 // T1被推斷為 map[int]bool
[]map[T1]T2 // T1被推斷為 int, T2被推斷為 bool

圖片

作為反例,“[]map[int]bool”顯然無法匹配這些類型:

int
struct{}
[]struct{}
[]map[T1]string
// etc...

函數參數類型推導(Function Argument Type Inference)

函數參數類型推導,顧名思義是在泛型函數被調用時,如果沒有被完全指定所有的類型參數,那么編譯器就會根據函數實際入參的類型,對類型參數所對應的具體類型進行推導,例如本文最開始的 Min 函數:

func Min[T constraints.Ordered](x, y T) T {
if x < y {
return x
}
return y
}

ans := Min(1, 2) // 此時類型參數T被推導為int

和其他支持泛型的語言一樣,Golang 的函數參數類型推導只支持“能夠從入參推導的類型參數”,如果類型參數用于標記返回類型,那么在使用時必須明確指定類型參數:

func MyFunc[T1, T2, T3 any](x T1) T2 {
// ...
var x T3
// ...
}

ans := MyFunc[int, bool, string](123) // 需要手動指定

類似這樣的函數,部分的類型參數僅出現在返回值當中(或者僅出現在函數體中,不作為入參或出參出現),就無法使用函數參數類型推導,而必須明確手動指定類型。

推導算法與示例

圖片

還是拿 Min 函數作為例子,講解一下函數參數類型推導的過程:

func Min[T constraints.Ordered](x, y T) T {
if x < y {
return x
}
return y
}

先來看看第一種情況:

Min(1, 2)

此時兩個入參均為無類型字面值常量,所以第一輪的類型統一化被跳過,且入參的具體類型沒有被確定,此時編譯器嘗試使用兩個參數的默認類型 int,由于兩個入參在函數定義處的類型都是“T”,且兩者都使用默認類型 int,所以此時 T 被成功推斷為 int。

然后來看第二種情況:

Min(1, int64(2))

此時第二個參數有一個明確的類型 int64,所以在第一輪的類型統一化中,T 被推斷為 int64,且在嘗試為第一輪漏掉的第一個參數“1”確定類型時,由于“1”是一個合法的 int64 類型值,所以 T 被成功推斷為 int64。

再來看第三種情況:

Min(1.5, int64(2))

此時第二個參數有一個明確的類型 int64,所以在第一輪的類型統一化中,T 被推斷為 int64,且在嘗試為第一輪漏掉的第一個參數“1.5”確定類型時,由于“1.5”不是一個合法的 int64 類型值,類型推導失敗,此時編譯器報錯。

最后看第四種情況:

Min(1, 2.5)

和第一種情況類似,第一輪的類型統一化被跳過,且兩個入參的具體類型沒有被確定,此時編譯器開始嘗試使用默認類型。兩個參數的默認類型分別是 int 和 float64,由于在類型推導中,同一個類型參數 T 只能被確定為一種類型,所以此時類型推導也會失敗。

約束類型推導(Constraints Type Inference)

約束類型推導是 Golang 泛型的另一個強大武器,它可以允許編譯器通過一個類型參數來推導另一個類型參數的具體類型,也可以通過使用類型參數來保存調用者的類型信息。

約束類型推導可以允許使用其他類型參數來為某個類型參數指定約束,這類約束被稱為“結構化約束”,這種約束定義了類型參數必須滿足的數據結構,例如:

// 將一個整數slice中的每個元素都x2后返回
func DoubleSlice[S ~[]E, E constraints.Integer](slice S) S {
ret := make(S, 0, len(slice))
for _, item := range slice {
ret = append(ret, item + item)
}
return ret
}

在這個函數的定義中,“~[]E”就是一個簡寫的對 S 的結構化約束,其完整寫法應是“interface{~[]E}”,即以類型集合的方式來定義的 interface,且其中只包含一種定義“~[]E”,意為“底層數據類型是[]E 的所有類型”。

注意,一個合法的結構化約束所對應的類型集合,應該滿足下列任意一個條件:

  1. 類型集合中只包含一種類型
  2. 類型集合中所有類型的底層數據類型均完全相同

在這個例子中,S 使用的結構化約束中,所有滿足約束的類型的底層數據類型均為[]E,所以是一個合法的結構化約束。

當存在無法通過函數參數類型推導確定具體類型的類型參數,且類型參數列表中包含結構化約束時,編譯器會嘗試進行約束類型推導。

推導算法與示例

圖片

簡單的例子

結合我們剛才的例子“DoubleSlice”函數,講一下約束類型推導的具體過程:

type MySlice []int

ans := DoubleSlice(MySlice{1, 2, 3})

在這個調用中,首先執行的是普通的函數參數類型推導,這一步會得到一個這樣的推導結果:

S => MySlice

此時編譯器發現,還有一個類型參數 E 沒有被推導,且當前存在一個使用結構化約束的類型參數 S,此時開始約束類型推導。

首先需要尋找已經完成類型推導的類型參數,在這個例子里是 S,它的類型已經被推導出是 MySlice。

然后會將 S 的實際類型“MySlice”,與 S 的結構化約束“~[]E”進行類型統一化,由于 MySlice 的底層類型是[]int,所以結構化匹配之后,得到了這樣的匹配結果:

E => int

此時所有的類型參數都已經被推斷,且符合各自的約束,類型推導結束。

一個更復雜的例子

假設有這樣一個函數:

func SomeComplicatedMethod[S ~[]M, M ~map[K]V, K comparable, V any](s S) {
// comparable 是一個內置的約束,表示所有可以使用 == != 運算符的類型
}

然后我們這樣去調用它:

SomeComplicatedMethod([]map[string]int{})

編譯時產生的類型推導過程如下,首先是函數參數類型推導的結果:

S => []map[string]int

然后對 S 使用約束類型推導,對比 []map[string]int 和 ~[]M,得到:

M => map[string]int

再繼續對 M 使用約束類型推導,對比 map[string]int 和 ~map[K]V,得到:

K => string
V => int

至此類型推導成功完成。

使用約束類型推導保存類型信息

約束類型推導的另一個作用就是,它能夠保存調用者的原始參數的類型信息。

還是以這一節的“DoubleSlice”函數做例子,假設我們現在實現一個更加“簡單”的版本:

func DoubleSliceSimple[E constraints.Integer](slice []E) []E {
ret := make([]E, 0, len(slice))
for _, item := range slice {
ret = append(ret, item + item)
}
return ret
}

這個版本只有一個類型參數 E。此時我們按照之前的方式去調用它:

type MySlice []int

ans := DoubleSliceSimple(MySlice{1, 2, 3}) // ans 的類型是 []int !!!

此時的類型推導僅僅是最基礎的函數參數類型推導,編譯器會對 MySlice 和[]E 直接做結構化比較,得出 E 的實際類型是 int 的結論。

此時 DoubleSliceSimple 這個函數返回的類型是[]E,也就是[]int,而不是調用者傳入的 MySlice。而之前的 DoubleSlice 函數,通過定義了一個使用結構化約束的類型參數 S,并且直接用 S 去匹配入參的類型,且返回值類型也是 S,就可以保留調用者的原始參數類型。

泛型的使用局限

目前 Golang 泛型依然還有不少的局限,幾個主要的局限點包括:

  1. 成員函數無法使用泛型
  2. 不能使用沒在約束定義中指定的方法,即使類型集合里所有的類型都實現了該方法
  3. 不能使用成員變量,即使類型集合里所有的類型都擁有該成員

下面分別舉例:

成員函數無法使用泛型

type MyStruct[T any] struct {
// ...
}

func (s *MyStruct[T]) Method[T2 any](param T2) { // 錯誤:成員函數無法使用泛型
// ...
}

在這個例子中,MyStruct[T]的成員函數 Method 定義了一個只屬于自己的函數參數 T2,然而這樣的操作目前是不被編譯器支持的(今后也很可能不會支持)。

無法使用約束定義之外的方法

type MyType1 struct {
// ...
}
func (t MyType1) Method() {}

type MyType2 struct {
// ...
}
func (t MyType2) Method() {}

type MyConstraint interface {
MyType1 | MyType2
}

func MyFunc[T MyConstraint](t T) {
t.Method() // 錯誤:MyConstraint 不包含 .Method() 方法
}

這個例子中,MyConstraint 集合中的兩個成員 MyType1 和 MyType2 盡管都實現了.Method()函數,但是也無法直接在泛型函數中調用。

如果需要調用,則應該將 MyConstraint 改寫為如下形式:

type MyConstraint interface {
MyType1 | MyType2
Method()
}

無法使用成員變量

type MyType1 struct {
Name string
}

type MyType2 struct {
Name string
}

type MyConstraint interface {
MyType1 | MyType2
}

func MyFunc[T MyConstraint](t T) {
fmt.Println(t.Name) // 錯誤:MyConstraint 不包含 .Name 成員
}

在這個例子當中,雖然 MyType1 和 MyType2 都包含了一個 Name 成員,且類型都是 string,也依然無法以任何方式在泛型函數當中直接使用。

因為類型約束本身是一個 interface,而 interface 的定義中只能包含類型集合,以及成員函數列表。

總結

Golang 1.18 帶來了上述三個非常重要的新特性,其中:

  1. 工作區模式可以讓本地開發的工作流更加順暢。
  2. 模糊測試可以發現一些邊邊角角的情況,提升代碼的魯棒性。
  3. 泛型可以讓一些公共庫的代碼更加優雅,避免像以前一樣,為了“通用性”不得不采用反射的方式,不僅寫起來難寫,讀起來難受,還增加了運行期的開銷,因為反射是運行時的動態信息,而泛型是編譯期的靜態信息。

本文也是簡單講了這幾方面的內容,希望能讓大家對 Golang 中的這些新玩意兒有一個基本的了解。

參考文獻

  1. https://go.dev/blog/go1.18
  2. https://go.dev/blog/intro-generics
  3. https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md
  4. https://go.dev/blog/get-familiar-with-workspaces
  5. https://go.dev/doc/tutorial/fuzz
  6. https://tonybai.com/2021/12/01/first-class-fuzzing-in-go-1-18/?
責任編輯:未麗燕 來源: 字節跳動技術團隊
相關推薦

2021-12-15 10:23:56

Go 1.18 Bet語言泛型

2021-12-28 07:20:44

泛型Go場景

2021-11-01 12:41:39

Go

2022-01-19 08:51:00

Module工作區Go

2021-10-29 10:55:07

Go 泛型語言

2021-10-18 10:53:26

Go 代碼技術

2021-07-29 09:20:18

Java泛型String

2021-12-15 12:59:56

Go泛型版Beta1

2021-09-29 18:17:30

Go泛型語言

2022-01-10 11:33:17

Go測試軟件

2022-11-27 23:37:34

Go模式Workspaces

2011-05-19 16:47:50

軟件測試

2022-12-14 23:05:29

Go模糊測試

2022-03-29 11:48:40

Go泛型測試

2021-12-30 19:34:15

Java泛型JDK

2023-11-29 08:19:45

Go泛型缺陷

2024-10-28 00:40:49

Go語法版本

2023-08-09 08:53:50

GoWASI語義

2023-09-26 01:21:34

2022-04-15 09:55:59

Go 泛型Go 程序函數
點贊
收藏

51CTO技術棧公眾號

国产91精品久久久| 精品盗摄一区二区三区| 亚洲免费在线精品一区| 国产精品欧美综合亚洲| 国语对白精品一区二区| 亚洲欧洲日产国产网站| 潘金莲激情呻吟欲求不满视频| 哥也色在线视频| 久久影院电视剧免费观看| 国产日产欧美a一级在线| 久久综合色综合| 精品欧美激情在线观看| 日韩精品一区二区三区视频在线观看| 男人靠女人免费视频网站| 男人天堂久久久| 波多野结衣中文字幕一区二区三区 | 嫩草视频免费在线观看| 九色porny丨首页入口在线| 中文天堂在线一区| 国产日韩二区| 国产三级按摩推拿按摩| 欧美亚洲网站| 欧美极品美女电影一区| 四虎永久免费地址| 亚洲第一二三区| 精品日韩在线观看| av在线网址导航| 高清不卡av| 一区二区三区四区在线免费观看| 日韩中文一区二区三区| 天堂网2014av| 韩国av一区二区三区四区| 国产999在线| 久久久午夜影院| 欧美激情自拍| 久久精品视频99| av手机在线播放| 亚洲理论电影片| 亚洲第一中文字幕| 亚洲国产欧美91| 日韩欧美三区| 欧美三级电影一区| 韩国一区二区av| 男女羞羞在线观看| 亚瑟在线精品视频| 欧美国产视频一区| a视频在线播放| 亚洲人成人一区二区在线观看 | 岛国av一区二区三区| 法国空姐在线观看免费| 色网站免费在线观看| 国产精品麻豆一区二区| 日韩av一区二区三区在线观看| 亚洲av成人精品一区二区三区在线播放 | 亚洲AV无码成人片在线观看| 国产精品一区2区| 亚洲一区中文字幕| a在线观看免费| 国产不卡在线视频| av成人观看| 欧美熟女一区二区| 99在线精品免费| 久久av一区二区三区亚洲| 色丁香婷婷综合久久| av午夜一区麻豆| 久久99精品久久久久久秒播放器 | 国产午夜精品在线观看| 日本不卡高清视频一区| 国产免费a∨片在线观看不卡| 久久精品这里都是精品| 日本精品一区二区三区不卡无字幕| av女名字大全列表| 久久久国际精品| 亚洲欧洲日本国产| 国产激情在线视频| 亚洲香蕉伊在人在线观| 夫妻免费无码v看片| 视频二区不卡| 欧美精品黑人性xxxx| 特黄特色免费视频| 欧美有码在线| 中文精品99久久国产香蕉| 日韩在线视频免费看| 欧美一区亚洲| 日本三级韩国三级久久| 在线观看日韩一区二区| 国产精品综合久久| 精品国产免费一区二区三区 | 婷婷综合福利| 在线成人激情视频| 全程偷拍露脸中年夫妇| 在线综合亚洲| 国产日产久久高清欧美一区| www.黄色一片| 久久久精品tv| 中文字幕色呦呦| 国产精品av一区二区三区| 欧美日韩高清一区| 亚洲国产精品狼友在线观看| 最新国产精品视频| 日韩在线观看免费高清| 久久精品久久精品久久| 亚洲综合二区| 亚洲一区二区三区四区在线播放| 涩涩视频免费看| 国产精品网站在线播放| 大陆av在线播放| 97欧美成人| 亚洲成人av在线播放| 亚洲最大成人综合网| 黄色欧美成人| 国产精品露脸av在线| 黄色福利在线观看| 国产精品伦一区二区三级视频| youjizz.com在线观看| 欧美日韩精品免费观看视欧美高清免费大片| 8v天堂国产在线一区二区| 国产又黄又粗又猛又爽的视频| 欧美丰满日韩| 日本一区二区三区四区视频| 精品国产va久久久久久久| 国产色一区二区| 日韩免费视频播放| 在线播放一区二区精品视频| 中文字幕免费精品一区高清| aaa人片在线| 国产精品综合一区二区三区| 色婷婷精品国产一区二区三区| 超碰97国产精品人人cao| 欧美日韩午夜在线视频| 毛茸茸多毛bbb毛多视频| 欧美韩日精品| 91aaaa| 91在线视频| 色香蕉成人二区免费| 97精品人妻一区二区三区蜜桃| 色狮一区二区三区四区视频| 欧美专区福利在线| 日本高清视频www| 亚洲欧美偷拍三级| 中文字幕线观看| 日韩黄色大片| 国产精品久久久久av免费| 深夜福利在线观看直播| 亚洲成人www| 国产一卡二卡三卡四卡| 欧美国产三区| 成人av免费在线看| 91精品久久久久久粉嫩| 欧美日韩1区2区| 999福利视频| 久久福利视频一区二区| 性欧美精品一区二区三区在线播放 | 5278欧美一区二区三区| 亚洲色图21p| 一本久久综合亚洲鲁鲁五月天 | 亚洲免费av网站| 一级日本黄色片| 欧美+日本+国产+在线a∨观看| 国产免费一区视频观看免费| 成人动漫在线播放| 欧美三级视频在线| 国产福利视频网站| 国产精品综合二区| 成年女人18级毛片毛片免费 | 亚洲精品狠狠操| 亚洲永久精品在线观看| 国产蜜臀97一区二区三区| 香蕉视频禁止18| 亚洲国产精品综合久久久| 亚洲一区二区三| 精品日韩av| 国产视频久久久| 中文在线字幕av| 亚洲天堂福利av| 性色av蜜臀av浪潮av老女人 | 亚洲综合无码一区二区| 亚洲无人区码一码二码三码| 亚洲一区二区毛片| 午夜精品视频在线观看一区二区 | 国严精品久久久久久亚洲影视| 韩国精品一区| 中文字幕精品一区久久久久 | 日韩av在线不卡| 中文字幕在线观看高清| 一区二区欧美在线观看| 波多野结衣办公室33分钟| 美女久久久精品| 久久久久久av无码免费网站下载| 豆花视频一区二区| 九九热在线精品视频| 超碰在线观看99| 欧美日韩一区二区免费在线观看| www.日本高清视频| 国产精品一区二区在线播放| 成人免费播放器| 视频精品在线观看| 国产成人精品日本亚洲11| 中文在线免费二区三区| 色婷婷久久av| 亚洲欧美另类综合| 欧洲激情一区二区| 欧美日韩国产精品综合| 久久先锋影音av| 91精产国品一二三产区别沈先生| 99国产精品视频免费观看一公开 | 亚洲日本在线观看| 波多野结衣视频播放| 久久99精品久久久| 自拍日韩亚洲一区在线| 日韩欧美二区| 国产尤物91| 国产精品久久久久久久久免费高清| 欧美精品在线播放| jzzjzzjzz亚洲成熟少妇| 日韩一区二区三区视频在线观看| 国产午夜小视频| 日韩毛片精品高清免费| 国产亚洲色婷婷久久99精品91| 麻豆一区二区在线| 玖玖精品在线视频| 日本成人7777| 国产成人免费观看| 亚洲欧美在线人成swag| 欧美在线观看视频| 自由的xxxx在线视频| 一区二区av在线| 无码国产精品一区二区免费16| 欧美丰满美乳xxx高潮www| 国产美女激情视频| 一区二区三区欧美| а天堂中文在线资源| 久久中文娱乐网| 疯狂揉花蒂控制高潮h| 国产成人精品一区二区三区四区| 天天操,天天操| 三级不卡在线观看| av免费在线播放网站| 91不卡在线观看| 亚洲图片小说在线| 国产精品白浆| 亚洲综合中文字幕在线观看| 欧美成人黄色| 成人有码在线视频| 国产精品天堂蜜av在线播放| 日韩美女中文字幕| 黄视频网站在线观看| 97热在线精品视频在线观看| 性欧美猛交videos| 久久精品一偷一偷国产| 国产在线视频网| 在线中文字幕日韩| 成人网视频在线观看| 亚洲深夜福利视频| 精品成人一区二区三区免费视频| 亚洲国产欧美精品| 天天干,夜夜操| 精品少妇一区二区三区日产乱码| 亚洲av少妇一区二区在线观看| 欧美一区二区三区思思人| 国产一区二区三区在线观看| 欧美性受xxxx| 国产乱人乱偷精品视频a人人澡| 欧美日韩国产一级| 一本久道久久综合无码中文| 欧美日韩国产电影| 亚洲精品一区二区三区不卡| 日韩精品一区二区三区在线观看| 国产成人手机在线| 日韩电影中文 亚洲精品乱码| 手机在线不卡av| 亚洲欧美日韩国产成人| 国产一二三在线观看| 色系列之999| 免费看a在线观看| 久久91超碰青草是什么| 亚洲奶水xxxx哺乳期| 国模精品视频一区二区三区| 男人久久天堂| 国产不卡一区二区在线播放| 天天综合91| 福利视频一区二区三区| 亚洲另类春色校园小说| 神马影院我不卡| 午夜精品av| 男人和女人啪啪网站| 日韩精品一级二级 | 久久国产综合精品| 久久出品必属精品| 成人性视频网站| 特级西西人体wwwww| 国产欧美日产一区| 久久久久久久久精| 欧美日韩在线视频首页| 五月激情丁香网| 欧美成人精品3d动漫h| 亚洲三级中文字幕| 日韩在线观看高清| 97人人在线视频| 国产在线精品播放| 久久av国产紧身裤| 视频在线观看成人| 在线成人www免费观看视频| 国产精品亚洲二区在线观看| 久久99精品久久久久久动态图| 在线视频日韩欧美| 国产肉丝袜一区二区| 欧美成人精品激情在线视频| 欧美性猛交xxxx久久久| 国产农村老头老太视频| 亚洲女人天堂av| 丝袜美腿av在线| 欧美洲成人男女午夜视频| jazzjazz国产精品麻豆| 亚洲高清视频一区二区| 在线国产日韩| 久久精品一二三四| 国产日韩欧美不卡| 国语对白一区二区| 日韩一级大片在线| 成年人在线视频免费观看| 欧美精品久久久久久久久| 羞羞影院欧美| 欧美极品jizzhd欧美| 午夜天堂精品久久久久| 狠狠热免费视频| 91社区在线播放| 青娱乐91视频| 欧美蜜桃一区二区三区| 韩国福利在线| 2018日韩中文字幕| ady日本映画久久精品一区二区| 一区二区av| 三级成人在线视频| 91成人破解版| 午夜电影一区二区| 精品人妻伦一区二区三区久久| 色yeye香蕉凹凸一区二区av| 超碰在线网站| 国产在线资源一区| 欧美日韩一卡| 激情视频免费网站| 国产精品情趣视频| 久久人人爽人人爽人人片av免费| 亚洲国产欧美一区二区三区久久| 日本一本在线免费福利| 国产日韩一区在线| 欧美限制电影| 色www免费视频| 国产精品热久久久久夜色精品三区| 色老头一区二区| 亚洲一区二区黄| 日韩精品免费观看视频| 欧美精品亚洲精品| 久久久亚洲一区| 欧美一区二区三区成人精品| 午夜av一区二区三区| 精品国产乱码一区二区三| 毛片精品免费在线观看| 成人综合日日夜夜| 日韩啊v在线| 激情文学综合插| 欧美一级特黄高清视频| 欧美丰满少妇xxxbbb| 手机在线免费av| 成人免费在线看片| 亚洲国产一区二区精品专区| 好吊一区二区三区视频| 精品久久久久久中文字幕| 亚洲欧美日韩精品永久在线| 国产成人福利视频| 成人直播大秀| www.久久av.com| 亚洲成a人在线观看| 日产精品久久久久久久性色| 97超级碰在线看视频免费在线看| 丝袜久久网站| 91国产精品视频在线观看| 久久久国产精品午夜一区ai换脸| 中文字幕男人天堂| 欧美成人午夜剧场免费观看| 另类视频一区二区三区| 欧美日韩性生活片| 国产午夜精品久久久久久久| 亚洲无码精品一区二区三区| 久久久精品免费| 国产精品毛片视频| 国产v亚洲v天堂无码久久久| 中文字幕av一区二区三区| 久久久久久久久毛片| 欧美日韩一区精品| 快射av在线播放一区| 91久久久在线| 亚洲美女网站| 久久av红桃一区二区禁漫| 亚洲成人久久网| 精品日韩视频| av在线com| 国产欧美日韩一区二区三区在线观看| 国产精品久久久久久免费播放 |