I understand the difference between arrays and slices in Go. But what I don't understand is why it is helpful to have arrays at all. Why is it helpful that an array type definition specifies a length and an element type? Why can't every "array" that we use be a slice?
Arrays are used to store multiple values of the same type in a single variable, instead of declaring separate variables for each value.
Slices in Go and Golang The basic difference between a slice and an array is that a slice is a reference to a contiguous segment of an array. Unlike an array, which is a value-type, slice is a reference type. A slice can be a complete array or a part of an array, indicated by the start and end index.
In Go language, arrays are mutable, so that you can use array[index] syntax to the left-hand side of the assignment to set the elements of the array at the given index. You can access the elements of the array by using the index value or by using for loop. In Go language, the array type is one-dimensional.
If you pass an array to a function, it will receive the copy of the array and not the pointer to it. Arrays are values, assigning one array to other copies all the elements. The size of an array is part of its type The type int[20] and int[10] are distinct.
There is more to arrays than just the fixed length: they are comparable, and they are values (not reference or pointer types).
There are countless advantages of arrays over slices in certain situations, all of which together more than justify the existence of arrays (along with slices). Let's see them. (I'm not even counting arrays being the building blocks of slices.)
1. Being comparable means you can use arrays as keys in maps, but not slices. Yes, you could say now that why not make slices comparable then, so that this alone wouldn't justify the existence of both. Equality is not well defined on slices. FAQ: Why don't maps allow slices as keys?
They don't implement equality because equality is not well defined on such types; there are multiple considerations involving shallow vs. deep comparison, pointer vs. value comparison, how to deal with recursive types, and so on.
2. Arrays can also give you higher compile-time safety, as the index bounds can be checked at compile time (array length must evaluate to a non-negative constant representable by a value of type int
):
s := make([]int, 3) s[3] = 3 // "Only" a runtime panic: runtime error: index out of range a := [3]int{} a[3] = 3 // Compile-time error: invalid array index 3 (out of bounds for 3-element array)
3. Also passing around or assigning array values will implicitly make a copy of the entire array, so it will be "detached" from the original value. If you pass a slice, it will still make a copy but just of the slice header, but the slice value (the header) will point to the same backing array. This may or may not be what you want. If you want to "detach" a slice from the "original" one, you have to explicitly copy the content e.g. with the builtin copy()
function to a new slice.
a := [2]int{1, 2} b := a b[0] = 10 // This only affects b, a will remain {1, 2} sa := []int{1, 2} sb := sa sb[0] = 10 // Affects both sb and sa
4. Also since the array length is part of the array type, arrays with different length are distinct types. On one hand this may be a "pain in the ass" (e.g. you write a function which takes a parameter of type [4]int
, you can't use that function to take and process an array of type [5]int
), but this may also be an advantage: this may be used to explicitly specify the length of the array that is expected. E.g. you want to write a function which takes an IPv4 address, it can be modeled with the type [4]byte
. Now you have a compile-time guarantee that the value passed to your function will have exactly 4 bytes, no more and no less (which would be an invalid IPv4 address anyway).
5. Related to the previous, the array length may also serve a documentation purpose. A type [4]byte
properly documents that IPv4 has 4 bytes. An rgb
variable of type [3]byte
tells there are 1 byte for each color components. In some cases it is even taken out and is available, documented separately; for example in the crypto/md5
package: md5.Sum()
returns a value of type [Size]byte
where md5.Size
is a constant being 16
: the length of an MD5 checksum.
6. They are also very useful when planning memory layout of struct types, see JimB's answer here, and this answer in greater detail and real-life example.
7. Also since slices are headers and they are (almost) always passed around as-is (without pointers), the language spec is more restrictive regarding pointers to slices than pointers to arrays. For example the spec provides multiple shorthands for operating with pointers to arrays, while the same gives compile-time error in case of slices (because it's rare to use pointers to slices, if you still want / have to do it, you have to be explicit about handling it; read more in this answer).
Such examples are:
Slicing a p
pointer to array: p[low:high]
is a shorthand for (*p)[low:high]
. If p
is a pointer to slice, this is compile-time error (spec: Slice expressions).
Indexing a p
pointer to array: p[i]
is a shorthand for (*p)[i]
. If p
is pointer to a slice, this is a compile time error (spec: Index expressions).
Example:
pa := &[2]int{1, 2} fmt.Println(pa[1:1]) // OK fmt.Println(pa[1]) // OK ps := &[]int{3, 4} println(ps[1:1]) // Error: cannot slice ps (type *[]int) println(ps[1]) // Error: invalid operation: ps[1] (type *[]int does not support indexing)
8. Accessing (single) array elements is more efficient than accessing slice elements; as in case of slices the runtime has to go through an implicit pointer dereference. Also "the expressions len(s)
and cap(s)
are constants if the type of s
is an array or pointer to an array".
May be suprising, but you can even write:
type IP [4]byte const x = len(IP{}) // x will be 4
It's valid, and is evaluated and compile-time even though IP{}
is not a constant expression so e.g. const i = IP{}
would be a compile-time error! After this, it's not even surprising that the following also works:
const x2 = len((*IP)(nil)) // x2 will also be 4
Note: When ranging over a complete array vs a complete slice, there may be no performance difference at all as obviously it may be optimized so that the pointer in the slice header is only dereferenced once. For details / example, see Array vs Slice: accessing speed.
See related questions where an array can be used / makes more sense than a slice:
Why use arrays instead of slices?
Why can't Go slice be used as keys in Go maps pretty much the same way arrays can be used as keys?
Hash with key as an array type
How do I check the equality of three values elegantly?
Slicing a slice pointer passed as argument
And this is just for curiosity: a slice can contain itself while an array can't. (Actually this property makes comparison easier as you don't have to deal with recursive data structures).
Must-read blogs:
Go Slices: usage and internals
Arrays, slices (and strings): The mechanics of 'append'
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With