In practice, Go's slices are two different data structures in one
As I've seen them in Go code and used them myself, Go's slices are generally used in two pretty distinctly separate situations. As a result, I believe that many people have two different conceptual models of slices and their behavior, depending on which situation they're using slices in.
The first model and use of slices is as views into a concrete array
(or string) that you already have in your code. You're taking an
efficient reference to some portion of the array and saying 'here,
deal with this chunk' to some piece of code. This is the use of
slices that is initially presented in A Tour of Go here and that is implicitly used
in, for example,
io.Writer, both of which
are given a reference to an underlying concrete byte array.
The second model and use of slices is as dynamically resizable
arrays. This is the usage where, for example, you start with '
accum string', and then add things to it with '
accum = append(accum,
...)'. In general, any code using
append() is using slices this
way, as is code that uses explicit slice literals ('
b, ..}'). Dynamically resizable arrays are a very convenient thing,
so this sort of slice shows up in lots of Go code.
(Part of this is that Go's type system strongly encourages you to use slices instead of arrays, especially in arguments and return values.)
Slices as dynamically resizable arrays actually have an anonymous backing store behind them, but you don't normally think about it; it's materialized, managed, and deallocated for you by the runtime and you can't get a direct reference to it. As we've seen, it's easy to not remember that the second usage of slices is actually a funny, GC-driven special case of the first sort of use. This can lead to leaking memory or corrupting other slices.
(It's not quite fair to call the anonymous backing array an implementation detail, because Go explicitly documents it in the language specification. But I think people are often going to wind up working that way, with the slice as the real thing they deal with and the backing array just an implementation detail. This is especially tempting since it works almost all of the time.)
This distinct split in usage and conceptual model (and the glitches that result at the edges of it) are why I've wound up feeling that in practice, Go's slices are two different data structures in one. The two concepts may be implemented with the same language features and runtime mechanisms, but people treat them differently and have different expectations and beliefs about them.