Why does `append(x[:0:0], x…)` copy a slice into a new backing array in Go?

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

Why does `append(x[:0:0], x...)` copy a slice into a new backing array in Go?

问题

在Go的slice tricks wiki和Go库(例如,这个示例)中,有时会看到以下类似的代码将一个切片复制到一个新的后备数组中。

// 在库中,可能是函数的最后...
return append(whateverSlice[:0:0], whateverSlice...)

// 在赋值语句中,就像在wiki示例中一样...
b = append(a[:0:0], a...)

以下是我理解的内容:

  • append函数的第二个参数是要复制到新后备数组中的切片的所有项。
  • append函数的第一个参数使用了完整的切片表达式(我们可以将第一个参数重写为a[0:0:0],但如果省略第一个0,它将被提供。我认为这与更大的意义无关。)
  • 根据规范,结果切片应该与原始切片具有相同的类型,并且长度和容量都为零。
  • (再次强调,这与直接相关,但我知道你可以使用copy而不是append,并且更容易阅读。)

然而,我仍然无法完全理解为什么语法append(someSlice[:0:0], someSlice...)会创建一个新的后备数组。最初我也对append操作为什么不会影响(或截断)原始切片感到困惑。

现在是我的猜测:

  • 我猜想所有这些操作都是必要且有用的,因为如果你只是赋值newSlice := oldSlice,那么对其中一个的更改将反映在另一个上。通常情况下,你不会希望出现这种情况。
  • 因为我们没有将append的结果赋值给原始切片(这在Go中是正常的),所以原始切片不会发生任何变化。它不会被截断或以任何方式改变。
  • 因为anySlice[:0:0]的长度和容量都为零,如果Go要将anySlice的元素分配给结果,它必须创建一个新的后备数组。这是为什么会创建一个新的后备数组的原因吗?
  • 如果anySlice...没有元素会发生什么?一个Go Playground上的片段表明,如果你在空切片上使用这个append技巧,复制和原始初始时具有相同的后备数组。(编辑:正如一位评论者解释的,我误解了这个片段。该片段显示这两个项最初都是相同的,但它们都没有后备数组。它们最初都指向一个通用的零值。)由于这两个切片的长度和容量都为零,一旦你向其中一个添加任何内容,那个切片就会获得一个新的后备数组。因此,我猜想,效果仍然是相同的。也就是说,在append进行复制后,这两个切片不能相互影响。
  • 另一个Playground片段表明,如果一个切片有多个元素,append复制方法会立即导致一个新的后备数组。在这种情况下,两个结果切片立即分离。

我可能对此过于担心,但我希望能更全面地解释为什么append(a[:0:0], a...)这个技巧的工作方式。

英文:

On Go's slice tricks wiki and Go libraries (e.g., this example), you sometimes see code like the following to copy a slice into a new backing array.

// In a library at the end of a function perhaps...
return append(whateverSlice[:0:0], whateverSlice...)

// In an assignment, as in the wiki example...
b = append(a[:0:0], a...)

Here's what I think I understand:

  • All of the items in the slice that is the second parameter to append are copied over to a new backing array.
  • In the first parameter to append, the code uses a full slice expression. (We can rewrite the first parameter as a[0:0:0], but the first 0 will be supplied if omitted. I assume that's not relevant to the larger meaning here.)
  • Based on the spec, the resulting slice should have the same type as the original, and it should have a length and capacity of zero.
  • (Again, not directly relevant, but I know that you can use copy instead of append, and it's a lot clearer to read.)

However, I still can't fully understand why the syntax append(someSlice[:0:0], someSlice...) creates a new backing array. I was also initially confused why the append operation didn't mess with (or truncate) the original slice.

Now for my guesses:

  • I'm assuming that all of this is necessary and useful because if you just assign newSlice := oldSlice, then changes to the one will be reflected in the other. Often, you won't want that.
  • Because we don't assign the result of the append to the original slice (as is normal in Go), nothing happens to the original slice. It isn't truncated or changed in any way.
  • Because the length and capacity of anySlice[:0:0] are both zero, Go must create a new backing array if it's going to assign the elements of anySlice to the result. Is this why a new backing array is created?
  • What would happen if anySlice... had no elements? A snippet on the Go Playground suggests that if you use this append trick on an empty slice, the copy and the original initially have the same backing array. (Edit: as a commenter explains, I misunderstood this snippet. The snippet shows that the two items are initially the same, but neither has a backing array yet. They both point initially to a generic zero value.) Since the two slices both have a length and capacity of zero, the minute you add anything to one of them, that one gets a new backing array. Therefore, I guess, the effect is still the same. Namely, the two slices cannot affect each other after the copy is made by append.
  • This other playground snippet suggests that if a slice has more than zero elements, the append copy method leads immediately to a new backing array. In this case, the two resulting slices come apart, so to speak, immediately.

I am probably worrying way too much about this, but I'd love a fuller explanation of why the append(a[:0:0], a...) trick works the way it does.

答案1

得分: 2

因为anySlice[:0:0]的长度和容量都为零,如果要将anySlice的元素分配给结果,Go必须创建一个新的支持数组。这就是为什么会创建一个新的支持数组的原因。

因为容量为0,是的。


https://pkg.go.dev/builtin@go1.19.3#append

如果目标切片具有足够的容量,它将被重新切片以容纳新的元素。如果没有足够的容量,将会分配一个新的底层数组。

  • 对于非空切片来说,cap=0是不足够的,需要分配一个新的数组。
英文:

> Because the length and capacity of anySlice[:0:0] are both zero, Go must create a new backing array if it's going to assign the elements of anySlice to the result. Is this why a new backing array is created?

Because capacity is 0, yes.


https://pkg.go.dev/builtin@go1.19.3#append

> If it has sufficient capacity, the destination is resliced to accommodate the new elements. If it does not, a new underlying array will be allocated.

  • cap=0 is NOT sufficient for non-empty slice, allocating a new array is necessary.

huangapple
  • 本文由 发表于 2022年12月8日 03:50:33
  • 转载请务必保留本文链接:https://go.coder-hub.com/74722224.html
匿名

发表评论

匿名网友

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

确定