1 回答

TA貢獻1841條經驗 獲得超3個贊
在您的代碼中,約束FooBar和stringer. 此外,這些方法是在指針接收器上實現的。
對你設計的程序的一個快速而骯臟的修復是簡單地斷言它*T確實是一個stringer:
func blah[T FooBar]() {
t := new(T)
do(any(t).(stringer))
}
游樂場:https ://go.dev/play/p/zmVX56T9LZx
但這放棄了類型安全,并可能在運行時出現恐慌。為了保持編譯時類型安全,另一種在某種程度上保留程序語義的解決方案是:
type FooBar[T foo | bar] interface {
*T
stringer
}
func blah[T foo | bar, U FooBar[T]]() {
var t T
do(U(&t))
}
那么這是怎么回事?
首先,類型參數與其約束之間的關系不是同一性:T 不是 FooBar。您不能T像以前那樣使用FooBar,因此*T絕對不等同于*fooor *bar。
因此,當您調用 時do(t),您試圖將一個類型*T傳遞給需要stringer,但是,指針或不是指針的東西,只是在其類型集中T沒有固有的方法。a() string
第 1 步:將方法添加a() string到FooBar接口中(通過嵌入stringer):
type FooBar interface {
foo | bar
stringer
}
但這還不夠,因為現在您的類型都沒有實際實現它。兩者都在指針接收器上聲明了方法。
第 2 步:將聯合中的類型更改為指針:
type FooBar interface {
*foo | *bar
stringer
}
此約束現在有效,但您還有另一個問題。當約束沒有核心類型時,您不能聲明復合文字。所以t := T{}也是無效的。我們將其更改為:
func blah[T FooBar]() {
var t T // already pointer type
do(t)
}
現在可以編譯了,但t實際上是指針類型的零值,所以它是nil. 您的程序不會崩潰,因為這些方法只返回一些字符串文字。
如果您還需要初始化指針引用的內存,則blah需要了解基本類型。
第 3 步:因此,您添加T foo | baras one type 參數,并將簽名更改為:
func blah[T foo | bar, U FooBar]() {
var t T
do(U(&t))
}
完畢?還沒有。轉換U(&t)仍然無效,因為兩者的類型集U不T匹配。您現在需要FooBar在T.
第 4 步:基本上,您將 的聯合提取FooBar到一個類型參數中,這樣在編譯時它的類型集將僅包括以下兩種類型之一:
type FooBar[T foo | bar] interface {
*T
stringer
}
現在可以使用 實例化約束T foo | bar,保留類型安全、指針語義并初始化T為非零。
func (f *foo) a() string {
fmt.Println("foo nil:", f == nil)
return "foo"
}
func main() {
blah[foo]()
}
印刷:
foo nil: false
foo
游樂場:https ://go.dev/play/p/src2sDSwe5H
如果你可以blah用指針類型實例化,或者更好地向它傳遞參數,你就可以刪除所有的中間技巧:
type FooBar interface {
*foo | *bar
stringer
}
func blah[T FooBar](t T) {
do(t)
}
func main() {
blah(&foo{})
}
- 1 回答
- 0 關注
- 158 瀏覽
添加回答
舉報