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

What's the difference between a generic slice argument and an argument constrained to slice types?

考虑实验包 slices。该包是实验性的,所以我知道签名可能会改变;我用它来说明问题。

考虑这个包中两个函数的签名,slices.Containsslices.Grow:

Contains 的第一个参数具有类型 []EE 的片段),Ecomparable 约束(可比较的类型) .

Grow 的第一个参数改为 S 类型(只是 S),S~[]E 约束(基础类型为 S一片 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())
}

甚至 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)
}

我理解的唯一实际差异是 slices.Grow 中的参数 s S 不是切片 。只是 constrained 来切片类型。事实上,当 arg 是 type MyUint64Slice []uint64:

的实例时,reflect.TypeOf(s) 会给出不同的输出

然而,我并不能立即看出两者之间的实用区别是什么。

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

问题

这两个声明在实践中是否等价?如果没有,我应该什么时候选择一个而不是另一个?

重要的是你必须return一个与参数类型相同(可能命名)的切片

如果你不需要 return 切片(只是一些其他信息,例如 bool 来报告是否包含该值),你不需要使用本身的类型参数对切​​片的约束,您可以仅对元素使用类型参数。

如果您必须 return 一个与输入相同类型的切片,您必须使用一个本身约束到切片的类型参数(例如 ~[]E)。

为了演示,让我们看看 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)]
}

如果您传递自定义类型的一个切片并将切片作为其基础类型,Grow() 可以 return 相同类型的值。 Grow2() 不能:它只能 return 未命名切片类型的值:[]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))

输出(在 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

如您所见,Grow2(y, 10) 收到类型 main.ints 的值,但它 return 接收类型 []int 的值。这不是我们想要的。