Go泛型系列:Go1.18 類型約束那些事
閱讀本文大概需要 6 分鐘。
大家好,我是 polarisxu。
上篇《Go泛型系列:提前掌握Go泛型的基本使用》簡單講解了泛型中的約束,但約束相關內容遠不止那些,本文介紹更多約束相關內容。
請安裝最新的 tip 版本,方便驗證本文的內容。當然,也可以通過 https://gotipplay.golang.org/?在線驗證。
01 語法變更
上次提到,定義約束的語法類似這樣:
type?Addable?interface?{
?type?int,?int8,?int16,?int32,?int64,?uint,?uint8,?uint16,?uint32,?uint64,?uintptr,?float32,?float64,?complex64,?complex128,?string
}
不過目前已經確認,語法改成如下形式:
type?Addable?interface?{
??int?|?int8?|?int16?|?int32?|?int64?|?uint?|?uint8?|?uint16?|?uint32?|?uint64?|?uintptr?|?float32?|?float64?|?complex64?|?complex128?|?string
}
對于這樣的修改,大家褒貶不一。就語義來說,新的方式?|?有或之意,更貼切,而且少了一個?type,更簡潔。
02 額外用法
除了以上提到的優(yōu)點,新的方式還還有額外的用途,也就是不用每次都定義一個接口約束。
看一個具體示例:
package?main
import?(
?"fmt"
)
func?add[T?int|float64](a,?b?T)?T?{
?return?a?+?b
}
func?main()?{
?fmt.Println(add(1,?2))
?fmt.Println(add(1.2,?2.3))
??//?fmt.Println(add("a",?"b"))
}
//?Output:
//?3
//?3.5
最后注釋的一行代碼,會編譯報錯:string does not satisfy int|float64。
注意 add 函數的泛型約束:T int|float64。如果約束是逗號分隔,無法采用這種語法:
func?add[T?int,float64](a,?b?T)?T
以上代碼顯然很不友好,編譯器也不好解析。
采用了?|?后,不需要每次都定義接口約束,可以讓代碼更少。不過,這種方式建議只在少數類型約束時才適合,否則可讀性太差。
除了以上用法,約束還有一種用法。
在 Go 語言中,基于某類型定義新類型,有時可能希望泛型約束是某類型的所有衍生類型。看一個具體例子:
package?main
import?(
?"fmt"
)
func?add[T?~string](x,?y?T)?T?{
?return?x?+?y
}
type?MyString?string
func?main()?{
?var?x?string?=?"ab"
?var?y?MyString?=?"cd"
?fmt.Println(add(x,?x))
?fmt.Println(add(y,?y))
}
//?Output:
//?abab
//?cdcd
注意 add 函數的簽名:
func?add[T?~string](x,?y?T)?T
約束?~string??表示支持 string 類型以及底層是 string 類型的類型,因此 MyString 類型值也可以傳遞給 add。
03 注意事項
約束形式的多樣性,導致 Go 泛型語法一下子復雜起來:
//?沒有任何約束
func?add[T?any](x,?y?T)?T
//?約束?Addble?(需要單獨定義)
func?add[T?Addble](x,?y?T)?T
//?約束允許?int?或?float64?類型
func?add[T?int|float64](x,?y?T)?T
//?約束允許底層類型是?string?的類型(包括?string?類型)
func?add[T?~string](x,?y?T)?T
在泛型中,有些場景可能想當然可以成立,但結果可能不成立,在使用時需要注意(當然,不排除將來支持)。比如:
func?MakeChan[T?chan?bool?|?chan?int](c?T)?{
??_?=?make(T)?//?錯誤
???_?=?new(T)?//?正確
??_?=?len(c)??//?正確
}
//?以下代碼無法編譯:
//?cannot?range?over?c?(variable?of?type?T?constrained?by?[]string|map[int]string)?(T?has?no?structural?type)
func?ForEach[T?[]string?|?map[int]string](c?T,?f?func(int,?string))?{
?for?i,?v?:=?range?c?{
??f(i,?v)
?}
}
ForEach 函數的簽名,你能看懂嗎?泛型確實讓 Go 復雜起來了,雖然語法允許,但建議大家以后寫泛型代碼時一定要盡量保證可讀性。
推薦閱讀
