Go泛型系列:Go1.18 類型約束那些事
閱讀本文大概需要 6 分鐘。
大家好,我是 polarisxu。
上篇《Go泛型系列:提前掌握Go泛型的基本使用》簡單講解了泛型中的約束,但約束相關(guān)內(nèi)容遠(yuǎn)不止那些,本文介紹更多約束相關(guān)內(nèi)容。
請安裝最新的 tip 版本,方便驗(yàn)證本文的內(nèi)容。當(dāng)然,也可以通過 https://gotipplay.golang.org/?在線驗(yàn)證。
01 語法變更
上次提到,定義約束的語法類似這樣:
type?Addable?interface?{
?type?int,?int8,?int16,?int32,?int64,?uint,?uint8,?uint16,?uint32,?uint64,?uintptr,?float32,?float64,?complex64,?complex128,?string
}
不過目前已經(jīng)確認(rèn),語法改成如下形式:
type?Addable?interface?{
??int?|?int8?|?int16?|?int32?|?int64?|?uint?|?uint8?|?uint16?|?uint32?|?uint64?|?uintptr?|?float32?|?float64?|?complex64?|?complex128?|?string
}
對于這樣的修改,大家褒貶不一。就語義來說,新的方式?|?有或之意,更貼切,而且少了一個(gè)?type,更簡潔。
02 額外用法
除了以上提到的優(yōu)點(diǎn),新的方式還還有額外的用途,也就是不用每次都定義一個(gè)接口約束。
看一個(gè)具體示例:
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
最后注釋的一行代碼,會編譯報(bào)錯(cuò):string does not satisfy int|float64。
注意 add 函數(shù)的泛型約束:T int|float64。如果約束是逗號分隔,無法采用這種語法:
func?add[T?int,float64](a,?b?T)?T
以上代碼顯然很不友好,編譯器也不好解析。
采用了?|?后,不需要每次都定義接口約束,可以讓代碼更少。不過,這種方式建議只在少數(shù)類型約束時(shí)才適合,否則可讀性太差。
除了以上用法,約束還有一種用法。
在 Go 語言中,基于某類型定義新類型,有時(shí)可能希望泛型約束是某類型的所有衍生類型。看一個(gè)具體例子:
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 函數(shù)的簽名:
func?add[T?~string](x,?y?T)?T
約束?~string??表示支持 string 類型以及底層是 string 類型的類型,因此 MyString 類型值也可以傳遞給 add。
03 注意事項(xiàng)
約束形式的多樣性,導(dǎo)致 Go 泛型語法一下子復(fù)雜起來:
//?沒有任何約束
func?add[T?any](x,?y?T)?T
//?約束?Addble?(需要單獨(dú)定義)
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
在泛型中,有些場景可能想當(dāng)然可以成立,但結(jié)果可能不成立,在使用時(shí)需要注意(當(dāng)然,不排除將來支持)。比如:
func?MakeChan[T?chan?bool?|?chan?int](c?T)?{
??_?=?make(T)?//?錯(cuò)誤
???_?=?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 函數(shù)的簽名,你能看懂嗎?泛型確實(shí)讓 Go 復(fù)雜起來了,雖然語法允許,但建議大家以后寫泛型代碼時(shí)一定要盡量保證可讀性。
我是 polarisxu,北大碩士畢業(yè),曾在 360 等知名互聯(lián)網(wǎng)公司工作,10多年技術(shù)研發(fā)與架構(gòu)經(jīng)驗(yàn)!2012 年接觸 Go 語言并創(chuàng)建了 Go 語言中文網(wǎng)!著有《Go語言編程之旅》、開源圖書《Go語言標(biāo)準(zhǔn)庫》等。
堅(jiān)持輸出技術(shù)(包括 Go、Rust 等技術(shù))、職場心得和創(chuàng)業(yè)感悟!歡迎關(guān)注「polarisxu」一起成長!也歡迎加我微信好友交流:gopherstudio
