The Complete Guide to Golang Arrays and Slices

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 // This gives a compilation error longerArr == arr // This gives a compilation error
      
      







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:



 // Struct equivalent for an array of length 4 type int4 struct { e0 int e1 int e2 int e3 int } // Struct equivalent for an array of length 5 type int5 struct { e0 int e1 int e2 int e3 int e5 int } arr := int4{3, 2, 5, 4} longerArr := int5{5, 7, 1, 2, 0}
      
      





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:



  1. Pointer to a sequence of data.
  2. The length (length), which determines the number of elements that are currently contained in the slice.
  3. 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} // slices of any length can be assigned to other slice types slice1 = slice2
      
      







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) // This creates a slice with length 0 and capacity 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} // unsorted sort.Ints(s) fmt.Println(s) // [1 2 3 4 5 6]
      
      







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:



  1. The number of integers is not specified (the number of integers for sorting can be any);
  2. 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:



  1. If an entity is described by a set of nonempty elements of a fixed length, use arrays.
  2. When describing a collection to which you want to add or from which to delete items, use slices.
  3. If the collection can contain any number of elements, use slices.
  4. 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.



All Articles