The basic collection data structure in Go is represented through arrays and slices. Although they may look similar, they have different connotations in their implementation. In short, Go arrays are not flexible, and are not meant to be associated with dynamic memory allocation. Slices, on the other hand, are abstractions built on top of these array types and are flexible and dynamic. This is the key reason why slices are more often used in Go. This Go programming tutorial explores this concept with explanations and Golang coding examples.
How Do You Create Arrays in Go?
An array is a homogeneous data structure of a fixed length. This simply means that the data items – or the elements of an array – are of the same type and can be anything from primitive types (such as int or string) to any self-defined types. The length of an array must be a constant – a positive integer value – and the compiler must know the length prior to compilation in order to allocate the memory. The maximum allowable memory allocation size is 2Gb for an array in Go. The size, once declared, is fixed and cannot be extended or reduced once the compilation is completed. This defines its static nature. The items are accessed through index, starting with 0 as the first element to the last element at index = lengthOfArray-1.
Typically, you declare an array in go in the following manner: var identifier[] type. Here is an example of how to declare Go arrays:
var arr[10] internal
Once an array is declared as an integer type (or any other numeric type, such as float, complex, byte, rune), each element is initialized with a default value of zero. The default value for a string type is “” (empty string) and for a bool, false. For types such as maps, channels, interfaces, and pointers, the default initialization is nil.
Go arrays are mutable. Values can be assigned at a specific index indicated by i, such as arr[i] = value. Referring to any value beyond the array size leads to panic or gives an array index out of range error.
Reading: How to Use Strings in Go (Golang)
Go Array Code Examples
Here is a quick example of how to declare an array, assign values, and extract elements in Go:
func main() { var ai [10]int var af [10]float64 var as [10]string ai[5] = 500 af[5] = 500,789 as[5] = “Hi” for i := 0; i < len(ai); i++ { fmt.Printf("%d,", ai[i]) } fmt.Println("n----------------") for i := 0; i < len(af); i++ { fmt.Printf("%f,", af[i]) } fmt.Println("n----------------") for i := 0; i < len(as); i++ { fmt.Printf("%v,", as[i]) } weekdays := [...]string{"sun", "mon", "tue", "wed", "thu", "fri", "sat"} for index := range weekdays { fmt.Println(weekdays[index]) } fmt.Println(weekdays[0]) }
Unlike C and C++ arrays, which are of pointer type, Go arrays are of value type. Therefore, we can use new() to create an array:
var ac = new([10]complex64) for i := range ac { fmt.Println(ac[i]) }
We also can assign one array to another array; in such a case a distinct copy of the array is created in memory. Any modification in the copied array has no bearing on the array from where this copy was created. Here is how to create an array from another arrays with Go code:
ac2:=ac; //ac2 is a distinct array with values copied from ac
This is significant because an array passed as an argument to a function is simply a copy of the array or pass by value. However, we can change this and pass an array by reference to a function using the & (ampersand) operator. Here is a quick example:
func main() { intArr := [5]int{11, 22, 33, 44, 55} rev1(intArr) for i := range intArr { fmt.Println(intArr[i]) } rev2(&intArr) for i := range intArr { fmt.Println(intArr[i]) } } func rev1(arr [5]int) { size := len(arr) for i, j := 0, size-1; i < size/2; i++ { arr[i]arr[j] = arr[j]arr[i] j-- } } func rev2(arr *[5]int) { size := len(arr) for i, j := 0, size-1; i < size/2; i++ { arr[i]arr[j] = arr[j]arr[i] j-- } }
Passing a big array to a function can quickly eat up memory space, especially if we pass a copy of it. Instead, we can pass a pointer to the array or use a slice of the array. Read more about using pointers and arrays in Go.
Reading: How to Use Pointers in Go
How Do You Initialize an Array in Go?
An array can be initialized in Go in a number of different ways. Here are some examples.
An array of 5 elements.
var intArray = [5] int {11, 22, 33, 44, 55}
We can omit the size as follows. In such a case the number of elements decides the size of the array at compile time:
var intArray = [] int {11, 22, 33, 44, 55}
Also, we can use ellipses (…) to decide the size of the array:
var intArray = […] int {11, 22, 33, 44, 55}
Additionally, an array can be assigned like a key/value pair:
var keyValueArray = [5]string{2: “Coffee”, 4: “Tea”}
In this case, the items with indexes 2 and 4 are initialized with string values; the others are set to a default empty string. If we do not provide the size, then the maximum key (index+1) becomes its length:
var keyValueArray = []string{2: “Coffee”, 3: “Tea”}
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. A slice, therefore, is also an array that pours a context of dynamism onto the underlying array, which otherwise is a static contiguous memory allocation.
Like an array, a slice is also indexable. However, its length can be changed at runtime. The minimum length is 0 and maximum length can be the length of the underlying array from where you get the slice from. Here is an example of a Go slice:
var intArray = [10] int {0, 1, 1, 2, 3, 5, 8, 13, 21, 34} var slice1 [] int = intArray[2:5] // index 2 to 4, size = 3 fmt.Println(“Length: “, len(slice1), ” Contents: “, slice1, ” Capacity: “, cap(slice1))
This would result in the following output:
Length: 3 Contents: [1 2 3] capacity: 8
The built-in cap() function indicates the length of a slice and how much it can grow – maximum length(intArray) – start-index (which is 2 in the above example). Therefore, 0 <= length(intArray) <= cap(slice1).
Since slices can represent a part of the same array, multiple slices can share data items. This is not possible with an array. Therefore, you could say the array actually is the building block of a slice.
The typical format of a slice declaration is: var identifier[] type. Note that it is not required to declare the size of a slice. A slice, by default, is set to nil and the starting length is 0. We can use slice expressions to indicate the start and end of a slice. Here are some examples:
var slice1[] int = intArray[0:len(intArray)] // slice consists of entire array var slice1[] int = intArray[:] // a shorthand to mean entire array var slice1= &intArray // also means entire array var slice1= intArray[2:] // same as intArray[2:len(intArray)]2nd till last var slice1= intArray[:5] // same as intArray[0:5)]1st to 5th -1
A slice in Go can be expanded to maximum size as shown in this example:
var slice2 = slice1[:cap(slice1)]
Reading: Understanding Functions in Go
Go Slices as Function Arguments
A slice is more efficient than an array when passed as a function argument. When the function is called, an array slice is created, and a reference to that is passed.. Here is a quick example of this concept in action:
package main import ( “fmt” ) func main() { var arr = [10]int{55, 99, 2, 11, 33, 77, 88, 2, 7, 1} sort(arr[:]) //entire array is passed for i := 0; i < len(arr); i++ { fmt.Printf("%d,", arr[i]) } } func sort(arr []int) { size := len(arr) isSwapped := false for i := 0; i < size; i++ { for j := 0; j < size-i-1; j++ { if arr[j] > arr[j+1] { isSwapped = true arr[j]arr[j+1] = arr[j+1]arr[j]
} } if !isSwapped { // no swap in pass, array is sorted break } } }
Note that Go developers should never use a pointer to a slice because slice itself is a reference type (ie, a pointer).
Using make() to create a slice in Go
It is not always necessary to create an array separately first, then create a slice from it. We can make a slice together with the array using the make() function like this example demonstrates:
var s1 []int = make([]int, 10)
Or, more conveniently, in this manner:
s2 := make([]int, 10)
This creates an array of size 10, then makes the slice reference to it. The make() function takes two arguments: the type of array to be created and the number of items. We can also make a slice that references a part of an array. The make() function has an extra optional parameter – cap – that denotes the capacity. For example:
s3 := make([]int, 10, 20) // s3 := new([20]int)[10:20]
This essentially means the same as:
s4 := new([20]int)[10:20]
This, however, does not mean that both new and make do the same thing, although both allocate memory in the heap. According to Golang docs, the basic difference is:
-
- The built-in function allocates and initializes an object of type slice, map, or chan (only). Like new, the first argument is a type, not a value. Unlike new, make’s return type is the same as the type of its argument, not a pointer to it. The specification of the result depends on the type.
- The new built-in function allocates memory. The first argument is a type, not a value, and the value returned is a pointer to a newly allocated zero value of that type.
Reading: Introduction to Go Methods
Final Thoughts on Arrays and Slices in Go
In this Go programming tutorial, we looked at how to use arrays and slices in Go. Understand that, at the end of the day, both use the same contiguous memory allocation. Think of slice as a pointer to an array, which makes it eligible for dynamic manipulation. An array is more of a static type but it is also the building block of a slice. A slice, as the name suggests, can reference a part of an array or an entire array. It goes without saying as you start to code and develop applications in Golang, you will use more slices than arrays for reasons highlighted in this developer tutorial.