Ben Chuanlong Du's Blog

It is never too late to learn.

Hands on Array in Golang

Things on this page are fragmentary and immature notes/thoughts of the author. Please read with your own judgement!

Comment

  1. Array in Golang is similar to Array in Rust in the sense that the length of an array is part of its type and must be determined at compile time.

  2. When an array is assigned to another variable or passed to a function as a parameter, it is copied! For this reason, array is not a good interface to use. Slice is prefer to array for function parameters.

In [14]:
import "reflect"

Construct Arrays

Create an integer array of 0's.

In [16]:
var arr[5]int
arr
Out[16]:
[0 0 0 0 0]
In [17]:
reflect.TypeOf(arr)
Out[17]:
[5]int

The length of an array is part of its type which means that it must be known at compile time. Specifying a (non-const) variable as the length of an array will the code fail to compile.

In [18]:
n := 10
var vals[n]int
vals
repl.go:2:10: array length is not a constant: [n]int

The value of a const variable is known at compile time, so can you specify a const integer variable as the length of an array.

In [19]:
const n = 10
var arr[n]int
arr
Out[19]:
[0 0 0 0 0 0 0 0 0 0]

Create a string array and explicitly specify the value at each index.

In [26]:
arr := [3]string{"how", "are", "you"}
arr
Out[26]:
[how are you]

Define an int array with inferred length. Note that ... must be used. If omitted, a slice instead of array is defined.

In [24]:
arr := [...]string{"how", "are", "you"}
arr
Out[24]:
[how are you]
In [25]:
reflect.TypeOf(arr)
Out[25]:
[3]string

Length of an Array

In [29]:
arr := [...]string{"how", "are", "you"}
arr
Out[29]:
[how are you]
In [30]:
len(arr)
Out[30]:
3

Indexing an Array

In [35]:
arr := [...]int{0, 10, 20, 30, 40, 50, 60, 70, 80, 90}
arr
Out[35]:
[0 10 20 30 40 50 60 70 80 90]
In [36]:
arr[0]
Out[36]:
0
In [37]:
arr[3]
Out[37]:
30
In [38]:
arr[3:7]
Out[38]:
[30 40 50 60]
In [39]:
reflect.TypeOf(arr[3:7])
Out[39]:
[]int

Loop Through an Array

The for ... range ... loop in Golang makes a copy of each value.

In [1]:
arr := [...]int{0, 1, 2}
arr
Out[1]:
[0 1 2]
In [2]:
for i, v := range arr {
    v *= 10
}
arr
Out[2]:
[0 1 2]
In [3]:
for i, v := range arr {
    arr[i] = v * 10
}
arr
Out[3]:
[0 10 20]

Slice

A Slice in Golang is similar to a dynamic array (or vector) in other programming languages. However, due to Golang's unique design (lacking of class), a Slice behaves different and sometimes confusing to users coming from other programming languages. A Slice in Golang is implemented as a descriptor (struct) containing a data pointer, a length and a capacity. Due to this design,

  1. When you pass a Slice (value copy) to a function in Golang, the function can update existing values in the Slice (as the copied Slice in the function shares the same underlying data pointer as the original Slice).
  2. The built-in function append can be used to append values into a slice. Since append takes a Slice (instead of a pointer to a Slice) by value, it cannot update the original Slice descriptor (struct), so it has to return a new Slice descriptor. This sounds like append isn't appending in place which is a misunderstanding.
    • When there's no capacity left in the underlying array, append has to allocate a new chunk of memory and copies value over. Appending isn't in place in this case, but it's true in any programming language.
    • When there's capacity left in the udnerlying array, appending happens in place in the underlying array (as no new memory allocation is required). However, since a new slice descriptor is returned by append and the origial slice descriptor stays the same, the original slice descriptor is still a view of the old array window instead of the new updated array window.
    • Sometimes, you want to pass a slice to another function for appending values and want the change to be reflected outside the function. To achieve this, you can pass a pointor to the Slice descriptor.
In [27]:
vec := []int{1, 2, 3}
vec
Out[27]:
[1 2 3]
In [28]:
reflect.TypeOf(vec)
Out[28]:
[]int

Create an integer slice with length 5 and capacity 5 using the function make.

In [4]:
s1 := make([]int, 5)
s1
Out[4]:
[0 0 0 0 0]
In [5]:
len(s1)
Out[5]:
5
In [6]:
cap(s1)
Out[6]:
5

Create an integer slice with length 5 and capacity 10 using the function make.

In [10]:
s2 := make([]int, 5, 10)
s2
Out[10]:
[0 0 0 0 0]
In [11]:
len(s2)
Out[11]:
5
In [12]:
cap(s2)
Out[12]:
10

Appending to a Slice

Appending to a slice

  1. Appending to
In [50]:
vec := []int{1, 2, 3}
vec
Out[50]:
[1 2 3]
In [51]:
vec = append(vec, 10, 20, 30)
vec
Out[51]:
[1 2 3 10 20 30]
In [52]:
v2 := []int{100, 200, 300, 400}
v2
Out[52]:
[100 200 300 400]
In [53]:
vec = append(vec, v2...)
vec
Out[53]:
[1 2 3 10 20 30 100 200 300 400]

Use Slice Instead of Array for Function Parameters

In [40]:
func updateSlice(s []int) {
    s[4] = 750
}
In [41]:
arr := [5]int{78, 89, 45, 56, 14}
arr
Out[41]:
[78 89 45 56 14]
In [44]:
updateSlice(arr[:])
arr
Out[44]:
[78 89 45 56 750]
In [1]:
vec := []int{78, 89, 45, 56, 14}
vec
parsing go files in TempDir "/tmp/gonb_e532465a": /tmp/gonb_e532465a/main.go:3:1: expected declaration, found vec
In [48]:
updateSlice(vec)
vec
Out[48]:
[78 89 45 56 750]
In [9]:
func printSlice(s string, x []int) {
	fmt.Printf("%s len=%d cap=%d %v\n",
		s, len(x), cap(x), x)
}

%%
a := make([]int, 0, 5)
printSlice("a", a)
b := append(a, 1)
printSlice("b", b)
printSlice("a", a)

hdr := (*reflect.SliceHeader)(unsafe.Pointer(&a))
data := *(*[5]int)(unsafe.Pointer(hdr.Data))
printSlice("a", data[:])
a len=0 cap=5 []
b len=1 cap=5 [1]
a len=0 cap=5 []
a len=5 cap=5 [1 0 0 0 0]
In [11]:
func printSlice(s string, x []int) {
	fmt.Printf("%s len=%d cap=%d %v\n",
		s, len(x), cap(x), x)
}

func myAppend(arr *[]int, val int) {
    *arr = append(*arr, val)
}

%%
a := make([]int, 0, 5)
printSlice("a", a)
myAppend(&a, 1000)
printSlice("a", a)
a len=0 cap=5 []
a len=1 cap=5 [1000]

References

Go array

Comments