近日,Go 官方部落格基於 2021 年 GopherCon 大會發表了一篇介紹新特性“泛型”的文章,作者為 Robert Griesemer 和 Ian Lance Taylor。
據介紹,Go 1.18 版本增加了對泛型的支援,泛型是自 Go 開源以來的最大改變。泛型是一種編寫正規化,它獨立於所使用的特定型別,泛型允許在函式和型別的實現中使用某個型別集合中的任何一種型別。
泛型為 Go 新增了三個新的重要內容:
- 面向函式和型別的“型別引數” (type parameters)
- 將介面型別定義為型別集合,包括沒有方法的介面型別
- 型別推斷:在許多情況下,在呼叫泛型函式時可省略型別引數(type arguments)
型別推斷
這是 Go 中最複雜的變更,包括:
- 函式引數型別推斷 (Function argument type inference)
- 約束型別推斷 (Constraint type inference)
雖然型別推斷的工作原理細節很複雜,但使用它並不複雜:型別推斷要麼成功,要麼失敗。如果成功,可以省略型別引數,呼叫泛型函式看起來與呼叫普通函式沒有什麼不同。如果型別推斷失敗,編譯器將給出錯誤訊息,在這種情況下,只需提供必要的型別引數。
type arguments
現在函式和型別都具有型別引數,型別引數列表看起來像一個普通的引數列表,除了它使用方括號而不是圓括號。
先從浮點值的基本非泛型 Min 函式開始:
func Min(x, y float64) float64 {
if x < y {
return x
}
return y
}
通過新增型別引數列表來使這個函式泛型化——使其適用於不同的型別。在此示例中,新增了一個帶有單個型別引數 T 的型別引數列表,並將float64
替換為T
。
import "golang.org/x/exp/constraints"
func GMin[T constraints.Ordered](x, y T) T {
if x < y {
return x
}
return y
}
現在就可以使用型別引數呼叫此函式
x := GMin[int](2, 3)
向 GMin
提供型別引數,在這種情況下 int
稱為例項化。例項化分兩步進行。首先,編譯器在泛型函式或泛型型別中用所有型別引數替換它們各自的型別引數。
其次,編譯器驗證每個型別引數是否滿足各自的約束。如果第二步失敗,例項化就會失敗,程式就會無效。成功例項化後,即可產生非泛型函式,它可以像任何其他函式一樣被呼叫。例如:
fmin := GMin[float64]
m := fmin(2.71, 3.14)
例項化 GMin\[float64\]
產生了一個與 Min
函式等效的函式,可以在函式呼叫中使用它。型別引數也可以與型別一起使用。
type Tree[T interface{}] struct {
left, right *Tree[T]
value T
}
func (t *Tree[T]) Lookup(x T) *Tree[T] { ... }
var stringTree Tree[string]
這裡泛型型別 Tree
儲存了型別引數T的值。泛型型別也可以有方法,如本例中的 Lookup
。為了使用泛型型別,它必須被例項化; Tree\[string\]
是使用型別引數 string
來例項化 Tree
的示例。
泛型是 Go 1.18 中一個重要的新語言特性,Robert Griesemer 和 Ian Lance Taylor 表示,這個功能實現得很好並且質量很高,但在生產環境中部署泛型程式碼時,還是需要謹慎行事。
部落格原文: