php小編西瓜將為您介紹如何在Go語言中使用泛型實例化類型參數的非零指針。在Go語言中,泛型是一種強大的特性,可以增加代碼的靈活性和重用性。當我們需要在泛型函數或方法中實例化一個非零指針時,可以使用類型斷言和反射來實現。通過使用這些技術,我們可以在運行時根據類型參數的具體類型來創建一個非零指針實例,從而實現泛型的靈活性和通用性。下面我們來詳細了解一下具體的實現方法。
問題內容
現在 golang/go:master 上提供了類型參數,我決定嘗試一下。看來我遇到了在類型參數提案中找不到的限制。 (或者我一定錯過了)。
我想編寫一個函數,它返回帶有接口類型約束的泛型類型值的切片。如果傳遞的類型是帶有指針接收器的實現,我們如何實例化它?
type SetGetter[V any] interface {
Set(V)
Get() V
}
// SetGetterSlice turns a slice of type V into a slice of type T,
// with T.Set() called for each entry in values.
func SetGetterSlice[V any, T SetGetter[V]](values []V) []T {
out := make([]T, len(values))
for i, v := range values {
out[i].Set(v) // panic if T has pointer receiver!
}
return out
}
登錄后復制
當使用 *Count 類型作為 T 調用上述 SetGetterSlice() 函數時,此代碼將在調用 Set(v) 時出現混亂。 (Go2go 游樂場)毫不奇怪,因為基本上代碼創建了 nil 指針的切片:
// Count implements SetGetter interface
type Count struct {
x int
}
func (c *Count) Set(x int) { c.x = x }
func (c *Count) Get() int { return c.x }
func main() {
ints := []int{1, 2, 3, 4, 5}
sgs := SetGetterSlice[int, *Count](ints)
for _, s := range sgs {
fmt.Println(s.Get())
}
}
登錄后復制
同一問題的變體
這個想法行不通,我似乎找不到任何簡單的方法來實例化指向的值。
out[i] = new(T) 將導致編譯失敗,因為它返回 *T,其中類型檢查器希望查看 T。調用
*new(T) 進行編譯,但會導致相同的運行時恐慌,因為 new(T) 返回 **Count 在這種情況下,其中指向 Count 的指針仍然是 nil。將返回類型更改為指向
T 的指針片段將導致編譯失?。?func SetGetterSlice[V any, T SetGetter[V]](values []V) []*T {
out := make([]*T, len(values))
for i, v := range values {
out[i] = new(T)
out[i].Set(v) // panic if T has pointer receiver
}
return out
}
func main() {
ints := []int{1, 2, 3, 4, 5}
SetGetterSlice[int, Count](ints)
// Count does not satisfy SetGetter[V]: wrong method signature
}
登錄后復制
解決方法
到目前為止我發現的唯一解決方案是要求將構造函數傳遞給泛型函數。但這感覺不對,而且有點乏味。如果 func F(T interface{})() []T 是完全有效的語法,為什么需要這樣做?
func SetGetterSlice[V any, T SetGetter[V]](values []V, constructor func() T) []T {
out := make([]T, len(values))
for i, v := range values {
out[i] = constructor()
out[i].Set(v)
}
return out
}
// ...
func main() {
ints := []int{1, 2, 3, 4, 5}
SetGetterSlice[int, *Count](ints, func() *Count { return new(Count) })
}
登錄后復制
摘要
我的問題(按優先順序排列):
-
我是否忽略了一些顯而易見的事情?
這是 Go 中泛型的限制嗎?這已經是最好的了嗎?
此限制是否已知,或者我應該在 Go 項目中提出問題嗎?
解決方法
基本上,您必須向約束添加一個類型參數,以使 T 可轉換為其指針類型。在最基本的形式中,該技術如下所示(帶有匿名約束):
func Foo[T any, PT interface { *T; M() }]() {
p := PT(new(T))
p.M() // calling method on non-nil pointer
}
登錄后復制
游樂場:https://www.php.cn/link/24aef8cb3281a2422a59b51659f1ad2e
分步解決方案
您的約束 SetGetter 已經聲明了類型參數 V,因此我們稍微修改上面的示例:
// V is your original type param
// T is the additional helper param
type SetGetter[V any, T any] interface {
Set(V)
Get() V
*T
}
登錄后復制
然后定義 SetGetterSlice 函數,其類型參數為 T any,其目的只是實例化約束 SetGetter。
然后您就可以將表達式 &out[i] 轉換為指針類型,并成功調用指針接收器上的方法:
// T is the type with methods with pointer receiver
// PT is the SetGetter constraint with *T
func SetGetterSlice[V any, T any, PT SetGetter[V, T]](values []V) []T {
out := make([]T, len(values))
for i, v := range values {
// out[i] has type T
// &out[i] has type *T
// PT constraint includes *T
p := PT(&out[i]) // valid conversion!
p.Set(v) // calling with non-nil pointer receiver
}
return out
}
登錄后復制
完整程序:
CFE57E536C89530D9A8C38E10967A10D
這變得更加冗長,因為 SetGetterSlice 現在需要三個類型參數:原始 V 加上 T (帶有指針接收器的類型)和 PT (新約束)。然而,當您調用該函數時,您可以省略第三個 – 通過類型推斷,實例化 PT SetGetter[V,T] 所需的類型參數 V 和 T 都是已知的:
SetGetterSlice[int, Count](ints)
登錄后復制
游樂場:https://www.php.cn/link/6b061fc28f7473418a006dfa832708b1






