通用切片参数和限制为切片类型的参数有什么区别?

bla*_*een 3 generics go slice type-parameter

考虑实验包slices。该软件包是实验性的,因此我知道签名可能会发生变化;我用它来说明问题。

考虑此包中两个函数的签名,slices.Contains并且slices.Grow

  • func Contains[E comparable](s []E, v E) bool

  • func Grow[S ~[]E, E any](s S, n int) S

第一个参数的Contains类型( s[]E的切片)受(可比较的类型)约束。EEcomparable

相反的第一个参数Grow具有类型S(只是S),并S~[]E(基础类型是 的切片的类型E)约束

然而,具有此类类型参数的函数内部允许执行的操作似乎没有任何实际差异。如果我们声明一些具有相同类型参数的伪函数,我们可以看到两者都编译得很好:

正如预期的那样,在这两个函数中,我们可以len/ capappendrange、 分配make和 索引[ ]

func fakeContains[E comparable](s []E, v E) {
    fmt.Println(len(s), cap(s))

    var e E
    fmt.Println(append(s, e))
    fmt.Println(make([]E, 4))

    for _, x := range s {
        fmt.Println(x)
    }
    fmt.Println(s[0])
    
    fmt.Println(reflect.TypeOf(s).Kind())
}

func fakeGrow[S ~[]E, E any](s S, n int) {
    fmt.Println(len(s), cap(s))

    var e E
    fmt.Println(append(s, e))
    fmt.Println(make(S, 4))

    for _, x := range s {
        fmt.Println(x)
    }
        fmt.Println(s[0])
    
    fmt.Println(reflect.TypeOf(s).Kind())
}
Run Code Online (Sandbox Code Playgroud)

甚至在所有情况下都reflect.TypeOf(s).Kind()给予。reflect.Slice

这些函数也可以用不同的类型进行测试,并且全部编译:

// compiles just fine
func main() {
    type MyUint64 uint64
    type MyUint64Slice []uint64

    foo := []uint64{0, 1, 2}
    fakeContains(foo, 0)
    fakeGrow(foo, 5)

    bar := []MyUint64{3, 4, 5}
    fakeContains(bar, 0)
    fakeGrow(bar, 5)

    baz := MyUint64Slice{6, 7, 8}
    fakeContains(baz, 0)
    fakeGrow(baz, 5)
}
Run Code Online (Sandbox Code Playgroud)

我的理解中唯一实际的区别是,slices.Grow参数中s S 不是一个 slice。它仅局限于切片类型。事实上,reflect.TypeOf(s)当 arg 是 的实例时,会给出不同的输出type MyUint64Slice []uint64

  • Contains与 args []E给出reflect.TypeOf(s) -> []uint64
  • Grow与 args S给出reflect.TypeOf(s) -> main.MyUint64Slice

然而,我并不清楚两者之间的实际区别是什么。

带有代码的游乐场:https://gotipplay.golang.org/p/zg2dGtSJwuI

问题

这两个声明在实践中是否等效?如果不是,我什么时候应该选择其中之一?

icz*_*cza 6

如果您必须返回与参数类型相同(可能命名)的切片,那么这一点很重要。

如果您不必返回切片(只需返回一些其他信息,例如 abool来报告是否包含该值),则不需要使用本身约束到切片的类型参数,您可以使用元素的类型参数仅有的。

如果必须返回与输入相同类型的切片,则必须使用本身限制为切片的类型参数(例如~[]E)。

为了进行演示,让我们看看以下 2 个实现Grow()

func Grow[S ~[]E, E any](s S, n int) S {
    return append(s, make(S, n)...)[:len(s)]
}

func Grow2[E any](s []E, n int) []E {
    return append(s, make([]E, n)...)[:len(s)]
}
Run Code Online (Sandbox Code Playgroud)

如果传递将切片作为其基础类型的自定义类型的切片,Grow()则可以返回相同类型的值。Grow2()不能:它只能返回未命名切片类型的值:[]E

以及演示:

x := []int{1}

x2 := Grow(x, 10)
fmt.Printf("x2 %T len=%d cap=%d\n", x2, len(x2), cap(x2))

x3 := Grow2(x, 10)
fmt.Printf("x3 %T len=%d cap=%d\n", x3, len(x3), cap(x3))

type ints []int
y := ints{1}

y2 := Grow(y, 10)
fmt.Printf("y2 %T len=%d cap=%d\n", y2, len(y2), cap(y2))

y3 := Grow2(y, 10)
fmt.Printf("y3 %T len=%d cap=%d\n", y3, len(y3), cap(y3))
Run Code Online (Sandbox Code Playgroud)

输出(在Go Playground上尝试):

x2 []int len=1 cap=12
x3 []int len=1 cap=12
y2 main.ints len=1 cap=12
y3 []int len=1 cap=12
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,Grow2(y, 10)接收一个 type 的值main.ints,但它返回一个 type 的值[]int。这不是我们想要的。