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

huangapple go评论81阅读模式
英文:

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

问题

考虑一下实验性包slices。由于该包是实验性的,所以我理解签名可能会发生变化;我使用它来说明问题。

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

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

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

Contains函数的第一个参数的类型是[]EE类型的切片),其中E受到comparable(可比较的类型)的约束。

Grow函数的第一个参数的类型是S(只是S),其中S受到~[]E(底层类型为E的切片类型)的约束。

然而,看起来在具有这种类型参数的函数内部允许的操作之间没有任何实际区别。如果我们声明一些具有相同类型参数的虚假函数,我们可以看到两者都可以编译通过:

如预期的那样,在这两个函数中,我们可以使用len/capappendrangemake进行分配,以及使用[ ]进行索引。

即使在所有情况下,reflect.TypeOf(s).Kind()也会给出reflect.Slice

这些函数还可以使用不同的类型进行测试,而且都可以编译通过:

唯一实际的区别在于,在slices.Grow中,参数s S不是切片。它只是受到切片类型的约束。事实上,当参数是type MyUint64Slice []uint64的实例时,reflect.TypeOf(s)会给出不同的输出:

  • 使用参数s []EContains函数会给出reflect.TypeOf(s) -> []uint64
  • 使用参数s SGrow函数会给出reflect.TypeOf(s) -> main.MyUint64Slice

然而,对我来说,这两者之间的实际区别并不明显。

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

英文:

Consider the experimental package slices. The package is experimental, so I understand the signatures may change; I'm using it to illustrate the issue.

Consider the signatures of two functions from this package, slices.Contains and slices.Grow:

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

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

The first argument to Contains has type []E (slice of Es) with E constrained by comparable (types that are comparable).

The first argument to Grow instead has type S (just S), with S constrained by ~[]E (types whose underlying type is a slice of E)

However it looks like there isn't any practical difference between what operations are allowed inside functions with such type params. If we declare some fake funcs with the same type parameters, we can see that both compile just fine:

As expected, in both functions we can len/cap, append, range, allocate with make, and index with [ ].

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())
}

Even reflect.TypeOf(s).Kind() gives reflect.Slice in all cases.

The functions can also be tested with different types, and all compile:

// 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)
}

The only actual difference in my understanding is that in slices.Grow the argument s S is not a slice. It's just constrained to slice types. And as a matter of fact reflect.TypeOf(s) gives a different output when the arg is an instance of type MyUint64Slice []uint64:

  • Contains with arg s []E gives reflect.TypeOf(s) -> []uint64
  • Grow with arg s S gives reflect.TypeOf(s) -> main.MyUint64Slice

However it's not immediately apparent to me what's the practical difference between the two.

Playground with the code: https://gotipplay.golang.org/p/zg2dGtSJwuI

Question

Are these two declarations equivalent in practice? If not, when should I choose one over the other?

答案1

得分: 6

如果你需要返回与参数相同(可能具有命名)类型的切片,那么这是很重要的。

如果你不需要返回一个切片(只需要其他一些信息,比如一个bool来报告值是否包含在内),你不需要使用一个类型参数来约束切片本身,你可以只使用一个元素的类型参数。

如果你需要返回与输入相同类型的切片,你必须使用一个类型参数来约束切片本身(例如~[]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()可以返回相同类型的值。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))

输出结果(在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的值,但它返回的是类型为[]int的值。这不是我们希望得到的结果。

英文:

It matters if you have to return a slice of the same (possibly named) type as the argument.

If you do not have to return a slice (just some other info e.g. a bool to report if the value is contained), you do not need to use a type parameter that itself constraints to a slice, you may use a type parameter for the element only.

If you have to return a slice of the same type as the input, you must use a type parameter that itself constraints to a slice (e.g. ~[]E).

To demonstrate, let's see these 2 implementations of 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)]
}

If you pass a slice of a custom type having a slice as its underlying type, Grow() can return a value of the same type. Grow2() cannot: it can only return a value of an unnamed slice type: []E.

And the demonstration:

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))

Output (try it on the 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

As you can see Grow2(y, 10) receives a value of type main.ints and yet it returns a value of type []int. This is not what we would want from it.

huangapple
  • 本文由 发表于 2022年2月10日 18:42:25
  • 转载请务必保留本文链接:https://go.coder-hub.com/71063663.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定