The translation of the article was prepared specifically for students of the Golang Developer course, classes for which begin today!
At first, it is easy to perceive arrays and slices as one and the same, but with different names: both are a data structure for representing collections. However, in reality they are very different from each other.
In this article, we will look at their differences and implementations in Go.
We will turn to examples so that you can make a more informed decision about where to apply them.
Arrays
An array is a collection of fixed size. The emphasis here is on a fixed size, because as soon as you set the length of the array, later you will not be able to change it.
Let's look at an example. We will create an array of four integer values:
arr := [4]int{3, 2, 5, 4}
Length and Type
In the example above, the variable
arr
defined as an array of type
[4]int
, which means that the array consists of four elements. It is important to note that size
4
included in the type definition.
It follows from this that, in fact, arrays of different lengths are arrays of different types. You cannot equate arrays of different lengths to each other and you cannot assign the value of one array to another in this case:
longerArr := [5]int{5, 7, 1, 2, 0} longerArr = arr
I found that arrays are easy to talk about in terms of structures. If you tried to create a
structure similar to an array, you most likely would have the following:
Actually, this is not recommended, but this is a good way to get an idea of why arrays of different lengths are arrays of different types.
Memory representation
The array is stored as a sequence of
n
blocks of a certain type:
This memory is allocated when you initialize an array variable.
Pass by link
Go does not have such a thing as passing by reference, instead everything is passed by value. If you assign the value of the array to another variable, then the assigned value will simply be copied.
If you want to pass only a “reference” to the array, use pointers:
When allocating memory and in a function, an array is actually a simple data type and works in much the same way as structures.
Slices
Slices can be considered as an extended implementation of arrays.
Slices were implemented in Go to cover some of the extremely common use cases developers encounter when working with collections, such as dynamically resizing collections.
A slice declaration is very similar to an array declaration, except that the length specifier is omitted:
slice := []int{4, 5, 3}
If you just look at the code, it seems that slices and arrays are quite similar, but their main difference lies in the implementation and conditions of use.
Memory representation
A slice is allocated differently than an array, and is essentially a modified pointer. Each slice contains three blocks of information:
- Pointer to a sequence of data.
- The length (length), which determines the number of elements that are currently contained in the slice.
- Capacity (capacity), which determines the total number of provided memory cells.
It follows that slices of different lengths can be assigned to each other. They are of the same type, and the pointer, length and volume can vary:
slice1 := []int{6, 1, 2} slice2 := []int{9, 3}
A slice, unlike an array, does not allocate memory during initialization. In fact, slices are initialized with a
nil
value.
Pass by link
When you assign a slice to another variable, you are still passing the value. Here, the value refers only to the pointer, length and volume, and not to the memory occupied by the elements themselves.
Adding New Items
To add new elements to the slice, you must use the
append
function.
nums := []int{8, 0} nums = append(nums, 8)
Under the hood, it will look like assigning a value specified for a new element, and after that - returning a new slice. The length of the new slice will be one more.
If, when adding an element, the length increases by one and thereby exceeds the declared volume, it is necessary to provide a new volume (in this case, the current volume usually doubles).
That is why it is most often recommended to create a slice with the length and volume specified in advance (especially if you clearly have an idea of what size the slice you need):
arr := make([]int, 0, 5)
What to use: arrays or slices?
Arrays and slices are completely different things, and therefore their use cases also vary.
Let's look at a few open source examples and the Go standard library to understand what to use and when.
Case 1: UUID
UUIDs are 128-bit pieces of data that are often used to mark an object or entity. Usually they are represented as hexadecimal values, separated by dashes:
e39bdaf4-710d-42ea-a29b-58c368b0c53c
In the
Google UUID library, the UUID is represented as an array of 16 bytes:
type UUID [16]byte
This makes sense since we know that the UUID consists of 128 bits (16 bytes). We are not going to add or remove any bytes from the UUID, and therefore the use of the array to represent it will be.
Case 2: Sorting Integer Values
In this example, we will use the
sort.Ints
function from
sort standard library :
s := []int{5, 2, 6, 3, 1, 4}
The
sort.Ints
function takes a slice of integers and sorts them in ascending order of values. Slices are preferable to use here for two reasons:
- The number of integers is not specified (the number of integers for sorting can be any);
- Numbers need to be sorted in ascending order. Using an array will ensure that the entire collection of integers is passed as a value, so the function will sort its own copy, not the collection passed to it.
Conclusion
Now that we’ve looked at the key differences between arrays and slices, as well as their use cases, I want to give some tips to make it easier for you to decide which design to use:
- If an entity is described by a set of nonempty elements of a fixed length, use arrays.
- When describing a collection to which you want to add or from which to delete items, use slices.
- If the collection can contain any number of elements, use slices.
- Will you change the collection in any way? If so, then slices should be used.
As you can see, slices cover most of the scenarios for building Go apps. However, arrays have a right to exist, and moreover, they are incredibly useful, especially when a suitable use case appears.