我正在學習 Go 1.18 泛型,我試圖理解為什么我在這里遇到麻煩。長話短說,我正在嘗試Unmarshal一個 protobuf,我希望引數型別blah“正常作業”。我已盡我所能簡化了問題,并且此特定代碼正在重現我看到的相同錯誤訊息:
./prog.go:31:5: cannot use t (variable of type *T) as type stringer in argument to do:
*T does not implement stringer (type *T is pointer to type parameter, not type parameter)
package main
import "fmt"
type stringer interface {
a() string
}
type foo struct{}
func (f *foo) a() string {
return "foo"
}
type bar struct{}
func (b *bar) a() string {
return "bar"
}
type FooBar interface {
foo | bar
}
func do(s stringer) {
fmt.Println(s.a())
}
func blah[T FooBar]() {
t := &T{}
do(t)
}
func main() {
blah[foo]()
}
我意識到我可以通過不使用泛型來完全簡化此示例(即,將實體傳遞給blah(s stringer) {do(s)}. 但是,我確實想了解錯誤發生的原因。
我需要對此代碼進行哪些更改,以便我可以創建一個實體T并將該指標傳遞給期望特定方法簽名的函式?
uj5u.com熱心網友回復:
在您的代碼中,約束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 | bar為一種型別引數,并將簽名更改為:
func blah[T foo | bar, U FooBar]() {
var t T
do(U(&t))
}
完畢?還沒有。轉換U(&t)仍然無效,因為兩者的型別集U和T不匹配。您現在需要FooBar在T.
第 4 步:基本上你將FooBarunion 提取到一個型別引數中,這樣在編譯時它的型別集將只包含以下兩種型別之一:
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{})
}
uj5u.com熱心網友回復:
https://golang.google.cn/blog/intro-generics
直到最近,Go 規范才說介面定義了一個方法集,大致就是介面中列舉的方法集。實作所有這些方法的任何型別都實作了該介面。
package main
import "fmt"
type stringer interface {
a() string
}
type foo struct{}
func (f foo) a() string {
return "foo"
}
type bar struct{}
func (b bar) a() string {
return "bar"
}
type FooBar interface {
stringer
}
func do(s stringer) {
fmt.Println(s.a())
}
func blah[T FooBar]() {
t := new(T)
do(*t)
}
func main() {
blah[foo]()
}
并且您可以洗掉 type FooBar,因為 FooBar(Generics) 與 stringer(Interface) 相同
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/460205.html
下一篇:使用不同的建構式引數實體化泛型類
