Arrays, Slices And Maps In Go

Strictly speaking, there are three kinds of first-level citizen container types in Go, array, slice and map. Sometimes, strings and channels can also be viewed as container types, but this article will not touch the two kinds of types. All container types talked about in the current article are arrays, slices and maps.

There are many container related details in Go. This article will list them one by one.

Simple Overview Of Container Types And Values

Each value of the three kinds of types is used to store a collection of element values. The types of all the elements stored in a container value are identical. The identical type is called the element type of (the container type of) the container value.

Each element in a container has an associated key. An element value can be accessed through its associated key. The key types of map types must be comparable types. The key types of array and slice types are all the built-in type int. The keys of the elements of an array or slice are non-negative integers which mark the positions of these elements in the array or slice. The non-negative integer keys are often called indexes.

Each container value has a length, which indicates how many elements are stored in that container. The valid range of the integer keys of an array or slice value is from zero (inclusive) to the length (exclusive) of the array or slice. For each value of a map type, the key values of that map value can be an arbitrary value of the key type of the map type.

There are also many differences between the three kinds of container types. Most of the differences originate from the differences between the value memory layouts of the three kinds of types. From the last article, value parts, we learned that an array value is composed of only one direct part, however a slice or map value may have an underlying part, which is referenced by the direct part of the slice or map value.

Elements of an array and a slice are both stored contiguously in a continuous memory segment. For an array, the continuous memory segment hosts the direct part of the array. For a slice, the continuous memory segment hosts the underlying part of the slice. The map implementaion of the standard Go compiler/runtime adopts the hashtable algorithm. So all elements of a map are also stored in an underlying continuous memory segment, but they may be not contiguous. There may be many holes (gaps) within the continuous memory segment.

Another common map implementation algorithm is the binary tree algorithm, but the following desciptions assume that the underlying structures of all maps are hashtables. Whatever algorithm is used, the keys associated with the elements of a map are also stored in the map.

We can access an element through its key. The time complexities of element accessments on all container values are all O(1), though, generally map element accessments are several times slower than array and slice element accessments.

Remember, the underlying parts of a value will not get copied when the value is copied. In other words, if a value has underlying parts, a copy of the value will share the underlying parts with the value. This is the root reason of many behavior differences between array and slice/map values. These behavior differences will be introduced below.

Literal Representations Of Unnamed Container Types

The literal representations of the three kinds of unnamed container types: where Here are some container type literal representation examples:
const Size = 32

type Person struct {
	name string
	age  int
}

// Array types.
[5]string
[Size]int
[16][]byte  // element type is a slice type: []byte
[100]Person // element type is a struct type: Person

// Slice types.
[]bool
[]int64
[]map[int]bool // element type is a map type: map[int]bool
[]*int         // element type is a pointer type: *int

// Map types.
map[string]int
map[int]bool
map[int16][6]string     // element type is an array type: [6]string
map[bool][]string       // element type is a slice type: []string
map[struct{x int}]*int8 // element type is a pointer type: *int8,
                        // and key type is a struct type.

We should try to avoid using large-size types as map key types. For it needs more time to compare (and calculate hashes of) keys of such types.

The sizes of all slice types are the same. The sizes of all map types are also the same. The size of an array type depends on its length and the size of its element type. The size of a zero-length array type or an array type with a zero-size element type is zero.

Container Value Literals

Like struct values, container values can also be represented with composite literals, T{...}, where T is a type. Here are some examples:
// An array value which contains four bool values.
[4]bool{false, true, true, false}

// A slice value which contains three words.
[]string{"break", "continue", "fallthrough"}

// A map value which contains some key-value pairs.
map[string]int{"C": 1972, "Python": 1991, "Java", 1995, "Go": 2009}

There are some variants for array and slice composite literals:
// The followings slice composite literals are all equivalent.
[]string{"break", "continue", "fallthrough"}
[]string{0: "break", 1: "continue", 2: "fallthrough"}
[]string{2: "fallthrough", 1: "continue", 0: "break"}
[]string{2: "fallthrough", 0: "break", "continue"}

// The followings array composite literals are all equivalent.
[4]bool{false, true, true, false}
[4]bool{0: false, 1: true, 2: true, 3: false}
[4]bool{1: true, true}
[4]bool{2: true, 1: true}
[...]bool{false, true, true, false}
[...]bool{3: false, 1: true, true}

In the last two literals, the ...s mean we want to let compilers deduce the lengths of array values represented by the literals.

The element values in a container composite literal must be assignable to the corresponding container element type. The key values in a map composite literal must be assignable to the corresponding map key type.

From above examples, we know that element indexes (keys) are optional in array and slice composite literals. If an index is present, it is not needed to be typed value of the key type int. But it must be a non-negative constant representable by a value of type int. And if it is typed, its type must be a basic integer type. An element which index is absent uses the previous element's index plus one. If the index of the first element is absent, its index is zero.

The keys in a map literal can be non-constants.

var a uint = 1
var _ = map[uint]int {a : 123} // okay
var _ = []int{a: 100}  // error: index must be non-negative integer constant
var _ = [5]int{a: 100} // error: index must be non-negative integer constant

Constant keys in one specific composite literal can't be duplicated.

Literal Representations Of Zero Values Of Container Types

Like structs, the zero value of an array type A can be represented with the composite literal A{}. For example, the zero value of type [100]int can be denoted as [100]int{}. All elements stored in the zero value of an array type are zero values of the element type of the array type.

Like pointer types, zero values of all slice and map types are represented with the predeclared nil.

BTW, there are some other kinds of types whose zero values are also represented by nil, including pointer, function, channel and interface types. Pointer types have been explained in pointers in Go. Other kinds of types will be explained in the following articles.

When an array variable is declared without being specified an initial value, memory has been allocated for the elements of the zero array value. The memory for the elements of a nil slice or map value has not been allocated yet.

Please note, []T{} represents a blank slice value (with zero elements) of slice type []T, it is different with []T(nil). The same situation is for map[K]T{} and map[K]T(nil).

Compisite Literals Are Unaddressable But Can Be Taken Addresses

Compisite Literals are unaddressable values. We have learned that struct composite literals can be taken addresses directly before. Container composite literals have no exceptions here.

Example:
package main

import "fmt"

func main() {
	pm := &map[string]int{"C": 1972, "Go": 2009}
	ps := &[]string{"break", "continue"}
	pa := &[...]bool{false, true, true, false}
	fmt.Printf("%T\n", pm) // *map[string]int
	fmt.Printf("%T\n", ps) // *[]string
	fmt.Printf("%T\n", pa) // *[4]bool
}

Nested Composite Literals Can Be Simplified

If a composite literal nested many other composite literals, then those nested composited literals can simplified to the form {...}.

For example, the slice value literal
// A slcie value of a type whose element type is *[4]byte.
// The element type is a pointer type whose base type is [4]byte.
// The pointer base type is an array type whose element type is byte.
var heads = []*[4]byte{
	&[4]byte{'P', 'N', 'G', ' '},
	&[4]byte{'G', 'I', 'F', ' '},
	&[4]byte{'J', 'P', 'E', 'G'},
}
can be simplified to
var heads = []*[4]byte{
	{'P', 'N', 'G', ' '},
	{'G', 'I', 'F', ' '},
	{'J', 'P', 'E', 'G'},
}

The array value literal in the following example
type language struct {
	name string
	year int
}

var _ = [...]language{
	language{"C", 1972},
	language{"Python", 1991},
	language{"Go", 2009},
}
can be simplified to
var _ = [...]language{
	{"C", 1972},
	{"Python", 1991},
	{"Go", 2009},
}

And the map value literal in the following example
type LangCategory struct {
	dynamic bool
	strong  bool
}

// A value of map type whose key type is a struct type and
// whose element type is another map type "map[string]int".
var _ = map[LangCategory]map[string]int{
	LangCategory{true, true}: map[string]int{
		"Python": 1991,
		"Erlang": 1986,
	},
	LangCategory{true, false}: map[string]int{
		"JavaScript": 1995,
	},
	LangCategory{false, true}: map[string]int{
		"Go":   2009,
		"Rust": 2010,
	},
	LangCategory{false, false}: map[string]int{
		"C": 1972,
	},
}
can be simplified to
var _ = map[LangCategory]map[string]int{
	{true, true}: {
		"Python": 1991,
		"Erlang": 1986,
	},
	{true, false}: {
		"JavaScript": 1995,
	},
	{false, true}: {
		"Go":   2009,
		"Rust": 2010,
	},
	{false, false}: {
		"C": 1972,
	},
}

Pleae notes, in the above several examples, the comma following the last item in each composite literal can't be omitted. Please read the line break rules in Go for more information later.

Compare Container Values

As which has mentioned in the article overview of Go type system, map and slice types are uncomparable types. So map and slice types can't be used as map key types.

Although a slice or map value can't be compared with another slice or map value (or itself), it can be compared to the bare untyped nil identifier to check whether or not the slice or map value is a zero value.

Most array types are comparable, except the ones whose element types are uncomparable types.

When comparing two values of the same array type, each of their elements will be compared. The two array values are equal only if all of their corresponding elements are equal.

Example:
package main

import "fmt"

func main() {
	var a [16]byte
	var s []int
	var m map[string]int
	
	fmt.Println(a == a)   // true
	fmt.Println(m == nil) // true
	fmt.Println(s == nil) // true
	fmt.Println(nil == map[string]int{}) // false
	fmt.Println(nil == []int{})          // false
	
	// The following lines fail to compile.
	/*
	_ = m == m
	_ = s == s
	_ = m == map[string]int(nil)
	_ = s == []int(nil)
	var x [16][]int
	_ = x == x
	var y [16]map[int]bool
	_ = y == y
	*/
}

Access And Modify Container Elements

The element associated to key k stored in a container value v can be represented with the syntax form v[k].

For a use of v[k], assume v is an array or slice, For a use of v[k], assume v is a map, then the type of k must be a comparable type. An example of container element accessments:
package main

import "fmt"

func main() {
	a := [3]int{-1, 0, 1}
	s := []bool{true, false}
	m := map[string]int{"abc": 123, "xyz": 789}
	fmt.Println (a[2], s[1], m["abc"])    // access
	a[2], s[1], m["abc"] = 999, true, 567 // modify
	fmt.Println (a[2], s[1], m["abc"])    // access
	
	n, present := m["hello"]
	fmt.Println(n, present, m["hello"]) // 0 false 0
	n, present = m["abc"]
	fmt.Println(n, present, m["abc"]) // 567 true 567
	m = nil
	fmt.Println(m["abc"]) // 0
	
	// The two lines fail to compile.
	/*
	_ = a[3]  // invalid array index 3 (out of bounds)
	_ = s[-1] // invalid slice index -1 (index must be non-negative)
	*/	

	// Each of the following lines causes a panic.
	_ = a[n]         // panic: runtime error: index out of range
	_ = s[n]         // panic: runtime error: index out of range
	m["hello"] = 555 // panic: assignment to entry in nil map
}

Container Assignments

If a map is assigned to another map, then the two maps will share all (underlying) elements.

Like map assignments, if a slice is assigned to another slice, they will share all (underlying) elements.

When an array is assigned to another array, all the elements are copied from the source one to the destination one. The two arrays don't share any elements.

Example:
package main

import "fmt"

func main() {
	m0 := map[int]int{0:7, 1:8, 2:9}
	m1 := m0
	m1[0] = 2
	fmt.Println(m0, m1) // map[0:2 1:8 2:9] map[0:2 1:8 2:9]
	
	s0 := []int{7, 8, 9}
	s1 := s0
	s1[0] = 2
	fmt.Println(s0, s1) // [2 8 9] [2 8 9]
	
	a0 := [...]int{7, 8, 9}
	a1 := a0
	a1[0] = 2
	fmt.Println(a0, a1) // [7 8 9] [2 8 9]
}

Check Lengths And Capacities Of Container Values

Besides the length property, each container value also has a capactiy property. The capacity of an array is always equal to the length of the array. The capacity of a map is unlimited. So, in practice, only capacities of slice values are meaningful. The capacity of a slice is always equal to or larger than the length of the slice. The meaning of slice capacities will be explained in the next section.

We can use the built-in len function to get the length of a container value, and use the built-in cap function to get the capacity of a container value. Each of the two functions returns an int result. As the capacity of any map value is unlimited, the built-in cap function doesn't apply to map values.

The length and capacity of an array value can never change. The length and capacity of a slice value may change at run time. So slices can be viewed as dynamic arrays. Slices are much more flexible than arrays and are used more popularly than arrays in practice.

Example:
package main

import "fmt"

func main() {
	var a [5]int
	fmt.Println(len(a), cap(a)) // 5 5
	var s []int
	fmt.Println(len(s), cap(s)) // 0 0
	s, s2 := []int{2, 3, 5}, []bool{}
	fmt.Println(len(s), cap(s), len(s2), cap(s2)) // 3 3 0 0
	var m map[int]bool
	fmt.Println(len(m)) // 0
	m, m2 := map[int]bool{1: true, 0: false}, map[int]int{}
	fmt.Println(len(m), len(m2)) // 2 0
}

In the above example, the length and capacity of each slice value are equal. This is not true for every slice values. We will use some slices whose respective lengths and capacities are not equal in the following sections.

Recall The Internal Structure Definition Of Slice Types

To understand slice types and values better and explain slices easier, we need to have an impression on the internal structure of slice types. From the last article, value parts, we learned that the internal structure of slice types defined by the standard Go compiler/runtime is like
type _slice struct {
	elements unsafe.Pointer // referencing underlying elements
	len      int            // length
	cap      int            // capacity
}

The internal structure definitions used by other compilers/runtimes implementations may be not the exact same but would be similar. The following explanations are based on the official slice implementation.

The above shown internal structure explains the memory layouts of the direct parts of slice values. The len field of the direct part of a slice indicates the length of the slice, and the cap field indicates the capacity of the slice. The following picture depicts the memory layout of a slice value.

Although the underlying memory segment which hosts the elements of a slice may be very large, the slice may be only aware of a sub-segment of the memory segment. For example, in the above picture, the slice is only aware of the middle grey sub-segment of the whole memory segment.

The next section will explain how to append elements to a base slice and yield a new slice by using the built-in append function. The result slice of an append function call may share starting elements with the base slice or not, depending on the capacity (and length) of the base slice and how many elements are appended.

For the slice depicted in the above picture, the elements from index len to index cap (exclusive) don't belong to the elements of the slice. They are just some redundant element slots (for the depicted slice, but they may be effective elements of other slices or another array).

When the slice is used as the base slice in an append function call,

The next section will show a picture which describes both of the two possible cases in appending slice elements.

There are more routes which lead to the elements of two slices are hosted on the same underlying memory segment. Such as assignments and the below to be introduced subslice operations.

Note, generally, we can't modify the three fields of a slice value individually, except through the reflection and unsafe ways. In other words, generally, to modify a slice value, its three fields must be modified together. Generally, this is achieved by copying/assigning another slice value (of the same slice type) to the slice which needs to be modified.

Append And Delete Container Elements

The syntax of appending a key-element pair (also called an entry) to a map is the same as the syntax of modifying a map element. For example, for a non-nil map value m, the following line
m[k] = e

put the key-element pair (k, e) into the map m if m doesn't contain an entry with key k, or modify the element value associated with k if m contains an entry with key k.

The underlying continuous memory segment of a map may need reallocated to hold more and more elements.

There is a built-in delete function which is used to delete an entry from a map. For example, the following line will delete the entry with key k from the map m. If the map m doesn't contain an entry with key k, it is a no-op, no panics will occur, even if m is a nil map.
delete(m, k)

The entries stored in a map are not ordered by their orders of being appended. Their orders are non-determinstic, we can think them are kind-of randomized.

An example shows how to append (put) elements to and delete elements from maps:
package main

import "fmt"

func main() {
	m := map[string]int{"Go": 2007}
	m["C"] = 1972     // append
	m["Java"] = 1995  // append
	fmt.Println(m)    // map[C:1972 Python:1991 Go:2007]
	m["Go"] = 2009    // modify
	delete(m, "Java") // delete
	fmt.Println(m)    // map[Go:2009 C:1972]
}

Array elements can neither be appended nor deleted, though elements of addressable arrays can be modified.

We can use the built-in append function to append multiple elements into a base slice. The result of the an append function call is a new slice which contains the elements of the base slice and the appended elements. Please note, the base slice is not modified by the append function call. Surely, if we expect, we can assign the result slice to the base slice to modify the base slice.

There is not a built-in way to delete an element from a slice. We must use the append function together with the subslice feature introduced below to achieve this goal. Slice element deletions and insertions will be demoed in the below more slice manipulations section. Here, the following example only shows how to append slice elements.

An example shows how to append elements to slices:
package main

import "fmt"

func main() {
	s0 := []int{2, 3, 5}
	s1 := append(s0, 7)      // append one element
	s2 := append(s1, 11, 13) // append two elements
	fmt.Println(s0, cap(s0)) // [2 3 5] 3
	fmt.Println(s1, cap(s1)) // [2 3 5 7] 6
	fmt.Println(s2, cap(s2)) // [2 3 5 7 11 13] 6
	s3 := append(s0)         // <=> s3 := s0
	s4 := append(s0, s0...)  // append all elements of s0
	fmt.Println(s3, cap(s3)) // [2 3 5] 3
	fmt.Println(s4, cap(s4)) // [2 3 5 2 3 5] 6
	s0[0], s1[0] = 99, 789
	fmt.Println(s2[0], s3[0], s4[0]) // 789 99 2
}

Note, the built-in append function is a variadic function. It has two parameters, the second one is a variadic parameter. There are two manners to call variadic functions. In the above example, line 7, line 8 and line 12 use one manner and line 13 uses the other manner. For the former manner, we can pass zero or more element values as the variadic arguments. For the latter manner, we must pass a slice as the only variadic argument and which must be followed by three dots .... We can learn how to call variadic functions from the article function types and values later.

Here, line 13 is equivalent to
	s4 := append(s0, s0[0], s0[1], s0[2])

For the three-dot ... manner, the append doesn't require the variadic argument must be a slice with the same type as the first slice argument, but their element types must be identical. In other words, the two argument slices must share the same underlying type.

In the above program,

So, s1 and s2 share some elements, s0 and s3 share all elements, and s4 doesn't share elements with others. The following picture depicted the memory layouts of these slices at the end of the above program.

Up to now (Go 1.10), the first arugment of an append function call can't be an untyped nil.

Create Slices And Maps With The Built-in make Function

Besides using composite literals to create map and slice values, we can also use the built-in make function to create map and slice values. The built-in make function can't be used to create array values.

(The built-in make function can also be used to create channels, which will be explained in the article channels in Go later.)

Assume M is a map type and n is non-negative integer, we can use the following two forms to create new maps of type M. The first form creates a new empty map which is allocated with enough space to hold the n elements. The second form only takes one argument, in which case a new empty map with enough space to hold a small number of elements is allocated. The small number is compiler dependent.
make(M, n)
make(M)

Assume S is a slice type, length and capacity are two non-negative integers, length is not larger than capacity, we can use the following two forms to create new slices of type S.
make(S, length, capacity)
make(S, length)

The first form creates a new slice with the specified length and capacity. The second form only takes two arguments, in which case the capacity of the new created slice is the same as its length.

All the elements in the result slice of a make function call are initialized as the zero value (of the slice element type).

An example on how to use the built-in make function to create maps and slices:
package main

import "fmt"

func main() {
	// Make new maps.
	fmt.Println(make(map[string]int)) // map[]
	m := make(map[string]int, 3)
	fmt.Println(m, len(m)) // map[] 0
	m["C"] = 1972
	m["Go"] = 2009
	fmt.Println(m, len(m)) // map[C:1972 Go:2009] 2
	
	// Make new slices.
	s := make([]int, 3, 5)
	fmt.Println(s, len(s), cap(s)) // [0 0 0] 3 5
	s = append(s, 7, 11)
	fmt.Println(s, len(s), cap(s)) // [0 0 0 7 11] 5 5
	s = make([]int, 2)
	fmt.Println(s, len(s), cap(s)) // [0 0] 2
}

Allocate Containers With The Built-in new Function

From the article pointers in Go, we learned that we can also call the built-in new function to allocate a value of any type and get a pointer which references the allocated value. The allocated value is a zero value of its type. For this reason, it is a nonsense to use new function to create map and slice values.

It is not totally a nonsense to allocate a zero value of an array type with the built-in new fucntion. However, it is seldom to do this in practice,

Example:
package main

import "fmt"

func main() {
	m := *new(map[string]int)   // <=> var m map[string]int
	fmt.Println(m == nil)       // true
	s := *new([]int)            // <=> var s []int
	fmt.Println(s == nil)       // true
	a := *new([5]bool)          // <=> var a [5]bool
	fmt.Println(a == [5]bool{}) // true
}

Composite literals and the built-in make fucntion are used much more popularly than the built-in new function to create container values.

Addressability Of Container Elements

Following are some facts about the addressabilities of container elements. For example:
package main

import "fmt"

func main() {
	a := [5]int{2, 3, 5, 7}
	s := make([]bool, 2)
	pa2, ps1 := &a[2], &s[1]
	fmt.Println(*pa2, *ps1) // 5 false
	a[2], s[1] = 99, true
	fmt.Println(*pa2, *ps1) // 99 true
	ps0 := &[]string{"Go", "C"}[0]
	fmt.Println(*ps0) // Go
	
	m := map[int]bool{1: true}
	_ = m
	// The following lines fail to compile.
	/*
	_ = &[3]int{2, 3, 5}[0]
	_ = &map[int]bool{1: true}[1]
	_ = &m[1]
	*/
}

Unlike most other unaddressable values, which direct parts can not be modified, the direct part of a map element values can be modified, but can only be modified (overwritten) as a whole. For most kinds of element types, this is not a big issue. However, if the element type of map type is an array type or struct type, things become some counter-intuitive.

From the last article, value parts, we learned that each of struct and array values is composed of only one direct part. So Example:
package main

import "fmt"

func main() {
	type T struct{age int}
	mt := map[string]T{}
	mt["John"] = T{age: 29}
	ma := map[int][5]int{}
	ma[1] = [5]int{1: 789}
	
	// Accessments are ok
	fmt.Println(ma[1][1]) // 789
	fmt.Println(mt["John"].age) // 29
	
	// The following two modifications fail to compile.
	/*
	ma[1][1] = 123      // error: cannot assign to a[1][1]
	mt["John"].age = 30 // cannot assign to struct field
	                    // mt["John"].age in map.
	*/
}

To make any expected modification work in the above example, the curresponding map element should be saved in a temporary variable firstly, then the temporary variable is modified as needed, in the end the curresponding map element is overwritten by the temporary variable. For example,
package main

import "fmt"

func main() {
	type T struct{age int}
	mt := map[string]T{}
	mt["John"] = T{age: 29}
	ma := map[int][5]int{}
	ma[1] = [5]int{1: 789}
	
	t := mt["John"] // a temporary copy
	t.age = 30
	mt["John"] = t // overwrite it back
	
	a := ma[1] // a temporary copy
	a[1] = 123
	ma[1] = a // overwrite it back
	
	fmt.Println(ma[1][1], mt["John"].age) // 123 30
}

Derive Slices From Arrays And Slices

We can derive a new slice from an array or slice by using the subslice syntax forms. The elements of the derived slice and the base array or slice are hosted on the same underlying continuous memory segment. In other words, the derived slice and the base array or slice may share some contiguous elements.

There are two subslice syntax forms (baseContainer is an array or slice):
baseContainer[low : high]       // two-index form
baseContainer[low : high : max] // three-index form
The two-index form is equivalent to
baseContainer[low : high : cap(baseContainer)]

So the two-idnex form is a special case of the three-index form. The two-index form is used munch more popularly than the three-index form in practice.

Note, the three-index form is only supported for Go 1.2+.

In a subslice expression, the low, high and max indexes must satisfy the following requirements:

Indexes not satisfying these requirements may make the subslice expression fail to compile at compile time or panic at run time, depending on the base container type kind and whether or not the indexes are constants.

Note,

The length of the result derived slice is equal to high - low, and the capacity of the result derived slice is equal to max - low. The length of a derived slice may be larger than the base container, but the capacity will never be larger than the base container.

In pratice, for simplicity, we often omitted some indexes in subslice syntax forms. The omission rules are: For example, the following expressions are equivalent.
baseContainer[0 : len(baseContainer)]
baseContainer[: len(baseContainer)]
baseContainer[0 :]
baseContainer[:]
baseContainer[0 : len(baseContainer) : cap(baseContainer)]
baseContainer[: len(baseContainer) : cap(baseContainer)]
An example of using subslice syntax forms:
package main

import "fmt"

func main() {
	a := [...]int{0, 1, 2, 3, 4, 5, 6}
	s0 := a[:]
	s1 := s0[:]   // <=> s1 := s0[0:7] <=> s1 := s0
	s2 := s1[1:3] // <=> s2 := s1[1:3] <=> s2 := a[1:3]
	s3 := s2[2:6] // <=> s3 := s2[2:6] <=> s3 := s1[3:7]
	s4 := s0[3:5] // <=> s4 := s0[3:5:7]
	s5 := s0[3:5:5]
	s6 := append(s4, 77)
	s7 := append(s5, 88)
	s8 := append(s7, 66)
	s3[1] = 99
	fmt.Println(len(s2), cap(s2), s2) // 2 6 [1 2]
	fmt.Println(len(s3), cap(s3), s3) // 4 4 [3 99 77 6]
	fmt.Println(len(s4), cap(s4), s4) // 2 4 [3 99]
	fmt.Println(len(s5), cap(s5), s5) // 2 2 [3 99]
	fmt.Println(len(s6), cap(s6), s6) // 3 4 [3 99 77]
	fmt.Println(len(s7), cap(s7), s7) // 3 4 [3 4 88]
	fmt.Println(len(s8), cap(s8), s8) // 4 4 [3 4 88 66]
}

The following picture depicts the final memory layouts of the array and slice values used in the above example.

From the picture, we know that the elements of slice s7 and s8 are hosted on a different underlying memory segment than the other containers. The elements of the other slices are hosted on the same memory segment hosting the array a.

Copy Slice Elements With The Built-in copy Function

We can use the built-in copy function to copy elements from one slice to another, the types of the two slices are not required to be identical, but their element types must be identical. In other words, the two argument slices must share the same underlying type. The first argument of the copy function is the destination slice and the second one is the source slice. The two arguments can overlap some elements. The copy function returns the number of elements copied, which will be the smaller one of the lengths of the two arguments.

With the help of the subslice syntax, we can use the copy function to copy elements between two arrays or between an array and a slice.

An example:
package main

import "fmt"

func main() {
	type Ta []int
	type Tb []int
	dest := Ta{1, 2, 3}
	src := Tb{5, 6, 7, 8, 9}
	n := copy(dest, src)
	fmt.Println(n, dest) // 3 [5 6 7]
	n = copy(dest[1:], dest)
	fmt.Println(n, dest) // 2 [5 5 6]
	
	a := [4]int{} // an array
	n = copy(a[:], src)
	fmt.Println(n, a) // 4 [5 6 7 8]
	n = copy(a[:], a[2:])
	fmt.Println(n, a) // 2 [7 8 7 8]
}

In fact, the copy function is not very essential. We can implement it by using the built-in append function, though our implementation doesn't support generic.
// Assume element type is T.
func Copy(dest, src []T) int {
	if len(dest) < len(src) {
		_ = append(dest[:0], src[:len(dest)]...)
		return len(dest)
	} else {
		_ = append(dest[:0], src...)
		return len(src)
	}
}

Although the copy function is not an essential function in Go, for many circumstances, it is more convenient that the just shown way.

From another point of view, the append function can also be viewed as a non-essential function (whereas copy is viewed as an essential function), for all its uses can be implemented with make and copy function calls.

Note, as a special case, the built-in copy function can be used to copy bytes from a string to a byte slice.

Up to now (Go 1.10), neither of the two arugments of a copy function call can be an untyped nil value.

Container Element Iterations

In Go, keys and elements of a container value can be iterated with the following syntax:
for key, element = range aContainer {
	// use key and element ...
}
where key and element are called iteration variables.

The assignment sign = can be a short variable declaration sign :=, in which case the two iteration variables are both two new declared variables which are only visible within the for-range code block body.

Like the traditional for loop block, each for-range loop block creates two code blocks, an implicit one and an explicit one which is formed by using {}. The explicit one is nested in the implicit one.

Like for loop blocks, break and continue statements can also be used in for-range loop blocks,

Example:
package main

import "fmt"

func main() {
	m := map[string]int{"C": 1972, "C++": 1983, "Go": 2009}
	for lang, year := range m {
		fmt.Printf("%v: %v \n", lang, year)
	}
	
	a := [...]int{2, 3, 5, 7, 11}
	for i, prime := range a {
		fmt.Printf("%v: %v \n", i, prime)
	}
	
	s := []string{"go", "defer", "goto", "var"}
	for i, keyword := range s {
		fmt.Printf("%v: %v \n", i, keyword)
	}
}

If the iteration variable i in the above example is declared outside of the for-range code block, its type must be the built-in type int.

The form for-range code block syntax has several variants:
// Ignore the key iteration variable.
for _, element = range aContainer {
	// ...
}

// Ignore the element iteration variable.
for key, _ = range aContainer {
	element = aContainer[key]
	// ...
}

// The element iteration variable is omitted.
// This form is equivalent to the last one.
for key = range aContainer {
	element = aContainer[key]
	// ...
}

// Ignore both the key and element iteration variables.
for _, _ = range aContainer {
	// This variant is not much useful.
}

// Both the key and element iteration variables are omitted.
// This form is equivalent to the last one.
for range aContainer {
	// This variant is not much useful.
}

Iterating over nil maps or nil slices is allowed, the number of iterated key-element pairs is zero for such cases.

Some details about iterations over maps are lsit here. If it is promised that there are no other goroutines manipulating a map m, then the following code is guaranteed to clear all key-element pairs stored in the map m:
for key := range m {
	delete(m, key)
}

Surely, array and slice elements can also be iterated by using the traditional for loop block:
for i := 0; i < len(anArrayOrSlice); i++ {
	element := anArrayOrSlice[i]
	// ...
}

For a for-range loop block
for key, element = range aContainer {...}
there are three important facts.
  1. The ranged container is a copy of aContainer. However, please note, only the direct part of aContainer is copied. The copied container is anonymous, so there are no ways to modify it.
    • If the aContainer is an array, then the modifications made on the array elements during the iteration will not be reflected to the iteration variables. The reason is that the clone of the array doesn't share elements with the array.
    • If the aContainer is a slice or map, then the modifications made on the slice or map elements during the iteration will be reflected to the iteration variables. The reason is that the clone of the slice (or map) shares all elements (entries) with the slice (or map).
  2. A key-element pair of the copy of aContainer will be assigned (copied) to the iteration variables at each iteration step, so the modifications made on the direct parts of the iteration variables will not be refelcted to the elements (and keys for maps) stored in aContainer.
  3. All key-element pairs will be assigned to the same iteration variables.
An example which proves the first and second facts.
package main

import "fmt"

func main() {
	type Person struct {
		name string
		age  int
	}
	persons := [2]Person {{"Alice", 28}, {"Bob", 25}}
	for i, p := range persons {
		fmt.Println(i, p)
		// This modification has no effects on the iteration,
		// for the ranged array is a copy of the persons array.
		persons[1].name = "Jack"
		// This modification has not effects on the persons array,
		// for p is just a copy of a copy of one persons element.
		p.age = 31
	}
	fmt.Println("persons:", &persons)
}
The output:
0 {Alice 28}
1 {Bob 25}
persons: &[{Alice 28} {Jack 25}]

If we change the array in the above to a slice, then the modification on the slice during the iteration has effects on the iteration, but the modification on the iteration variable still has no effects on the slice.
package main

import "fmt"

func main() {
	type Person struct {
		name string
		age  int
	}
	persons := []Person {{"Alice", 28}, {"Bob", 25}} // a slice
	for i, p := range persons {
		fmt.Println(i, p)
		// Now this modification has effects on the iteration.
		persons[1].name = "Jack"
		// This modification still has no any real effects.
		p.age = 31
	}
	fmt.Println("persons:", &persons)
}
The output becomes to:
0 {Alice 28}
1 {Jack 25}
persons: &[{Alice 28} {Jack 25}]

An example to prove the second and third facts.
package main

import "fmt"

func main() {
	langs := map[struct{ dynamic, strong bool }]map[string]int{
		{true, false}:  {"JavaScript": 1995},
		{false, true}:  {"Go": 2009},
		{false, false}: {"C": 1972},
	}
	// The key type and element type of this map are both
	// pointer types. Some weird, just for education purpose.
	m0 := map[*struct{ dynamic, strong bool }]*map[string]int{}
	for category, langInfo := range langs {
		m0[&category] = &langInfo
		// This following line has no real effects on langs.
		category.dynamic, category.strong = true, true
	}
	m1 := map[struct{ dynamic, strong bool }]map[string]int{}
	for category, langInfo := range m0 {
		m1[*category] = *langInfo
	}
	// m0 and m1 both contain only one entry.
	fmt.Println(len(m0), len(m1), m1)
	for category, langInfo := range langs {
		fmt.Println(category, langInfo)
	}
}
As above has mentioned, the entry iteration order is randomized, so the output of the above program may be also randomized. One possible output:
1 1 map[{true true}:map[C:1972]]
{true false} map[JavaScript:1995]
{false true} map[Go:2009]
{false false} map[C:1972]

The cost of copying a slice or map is small, but the cost of copying an array is large if the size of the array type is large. So, generally, it is not a good idea to range over a large array. We can range over a slice derived from the array, or range over a pointer to the array (see the next section for details).

For an array or slice, if the size of its element type is large, then, generally, it is also not a good idea to use the second iteration variable to store the iterated element at each loop step. For such arrays and slices, we should use the one-iteration-variable for-range loop variant or the traditional for loop to iterate their elements. In the following example, the loop in function fa is much less efficient than the loop in function fb.
type Buffer struct {
	start, end int
	data       [1024]byte
}

func fa(buffers []Buffer) int {
	numUnreads := 0
	for _, buf := range buffers {
		numUnreads += buf.end - buf.start
	}
	return numUnreads
}

func fb(buffers []Buffer) int {
	numUnreads := 0
	for i := range buffers {
		numUnreads += buffers[i].end - buffers[i].start
	}
	return numUnreads
}

Use Array Pointers As Arrays

In many situations, we can use a pointer to an array as the array.

We can range over a pointer to an array to iterate the elements of the array. For arrays with large lengths, this way is much more efficient, for copying a pointer is much more efficient than copying a large-size array. In the following example, the two loop blocks are equivalent and both are efficient.
package main

import "fmt"

func main() {
	var a [100]int
	
	for i, n := range &a { // copying a pointer is cheap
		fmt.Println(i, n)
	}
	
	for i, n := range a[:] { // copying a slice is cheap
		fmt.Println(i, n)
	}
}

If the second iteration is neither ignored nor omitted, then range over a nil array pointer will panic. In the following example, each of the first two loop blocks will print five indexes, however, the last one will produce a panic.
package main

import "fmt"

func main() {
	var p *[5]int // nil
	
	for i, _ := range p { // okay
		fmt.Println(i)
	}
	
	for i := range p { // okay
		fmt.Println(i)
	}
	
	for i, n := range p { // panic
		fmt.Println(i, n)
	}
}

Array pointers can also used to index array elements. Indexing array eleemnts through a nil array pointer produces a panic.
package main

import "fmt"

func main() {
	a := [5]int{2, 3, 5, 7, 11}
	p := &a
	p[0], p[1] = 17, 19
	fmt.Println(a) // [17 19 5 7 11]
	p = nil
	p[0] = 31 // panic
}

We can also derived slices from array pointers. Deriving slices from a nil array pointer produce a panic.
package main

import "fmt"

func main() {
	pa := &[5]int{2, 3, 5, 7, 11}
	s := pa[1:3]
	fmt.Println(s) // [3 5]
	pa = nil
	s = pa[1:3] // panic
}

We can also pass array pointers as the arguments of the built-in len and cap functions. Nil array pointer arguments for the two functons will not produce panics.
var pa *[5]int // nil
fmt.Println(len(pa), cap(pa)) // 5 5

The memclr Optimization

Assume t0 is a literal presentation of the zero value of type T, and a is an array which element type is T, then the standard Go compiler will translate the following one-iteration-variable for-range loop block
for i := range a {
	a[i] = t0
}

to an internal memclr call, generally which is faster than resetting each element one by one.

The optimization also works if the ranged container is a slice. Sadly, it doesn't work if the ranged value is an array pointer (up to Go 1.10). So if you want to reset an array, don't range its pointer. In particular, it is recommended to range a slice derived from the array, like this:
s := a[:]
for i := range s {
	s[i] = t0
}

The reason of why is is recommended to range over a derived slice is it is very possible that other Go compilers don't make the above optimization, and as above has mentioned, ranging over an array will make a copy of the array (though the standard Go compiler won't in this optimization).

Calls To The Built-in len And cap Functions May Be Evaluated At Compile Time

If the argument passed to a built-in function len or cap function call is an array or an array pointer value, then the call is evaluated at compile time and the ressult of the call is a typed constant with default type as the built-in type int. The result can be bound to named constants.

Example:
package main

import "fmt"

var a [5]int
var p *[7]string

// N and M are both typed constants.
const N = len(a)
const M = cap(p)

func main() {
	fmt.Println(N) // 5
	fmt.Println(M) // 7
}

Modify The Length And Capacity Properties Of A Slice Individually

As above has mentioned, generally, the length and capacity of a slice value can't be modified individually. A slice value can only be overwritten as a whole by assigning another slice value to it. However we can modify the length and capacity of a slice individually by using reflections.

Example:
package main

import (
	"fmt"
	"reflect"
)

func main() {
	s := make([]int, 2, 6)
	fmt.Println(len(s), cap(s)) // 2 6
	
	reflect.ValueOf(&s).Elem().SetLen(3)
	fmt.Println(len(s), cap(s)) // 3 6
	
	reflect.ValueOf(&s).Elem().SetCap(5)
	fmt.Println(len(s), cap(s)) // 3 5
}

The argument passed to the reflect.SetLen function must not be larger than the current capacity of the argument slice s. The argument passed to the reflect.SetCap function must not be smaller than the current length of the argument slice s and larger than the current capacity of the argument slice s.

The reflection way is very inefficient, it is slower than a slice assignment.

More Slice Manipulations

Go doesn't support more slice operations, such as slice clone, element deletion and insertion, in the built-in way. We must compose the built-in ways to achieve those operations.

In the following examples in the current section, assume s is the talked slice, T is its element type and t0 is a zero value literal representation of T.

Clone Slices

Up to the official Go SDK 1.10, the best way to clone a slice is to use the following way.
sClone := append(s[:0:0], s...)
For slices with large lengths (throunsands of elements), the above way is more efficient than
sClone := make([]T, len(s))
copy(sClone, s)

The second way is not perfect as the first one. If s is a nil slice, the second way results a non-nil clone.

Delete A Segment Of Slice Elements

Above has mentioned that the elements a slice are stored contiguously in memory and there are no gaps between any two adjacent elements of the slice. So when a slice element is removed,
  • if the element order must be preserved, then each of the subsequent elements followed the removed element must be moved forwards.
  • if the element order doesn't need to be preserved, then we can move the last element in the slice to the removed index.
In the following example, assume from and to are two legal indexes, from is not larger than to, and the to index is exclusive.
// way 1 (preserve element orders):
s = append(s[:from], s[to:]...)

// way 2 (preserve element orders):
s = s[:from + copy(s[from:], s[to:])]

// Don't preserve element orders:
copy(s[from:to], s[len(s)+from-to:])
s = s[:len(s)+from-to]
If the slice elements contain pointers, we should reset tail elements in the old slice to avoid memory leaking.
// "len(s)+to-from" is the old slice length.
temp := s[len(s):len(s)+to-from]
for i := range temp {
	temp[i] = t0
}

As above has mentioned, the for-range loop code block will be optimized as a memclr call by the standard Go compiler.

Delete One Slice Element

Deleting one element is similar to, and also simpler than, deleting a segment of elements.

In the following example, assume i the index of the element to be removed and i is a legal index.
// Way 1 (preserve element orders):
s = append(s[:i], s[i+1:]...)

// Way 2 (preserve element orders):
s = s[:i + copy(s[i:], s[i+1:])]

// There will be len(s)-i-1 elements being copied in
// in either of the above two ways.

// Don't preserve element orders:
s[i] = s[len(s)-1]
s = s[:len(s)-1]

If the slice elements contain pointers, then after the deletion action, we should reset the last element of the old slice value to avoid memory leaking:
s[len(s):][0] = t0

Delete Slice Elements Conditionally

Sometimes, we may need to delete slice elements by some conditions.
// Assume T is a small-size type.
func DeleteElements(s []T, shouldKeep func(T) bool, clear bool) []T {
	result := make([]T, 0, len(s))
	for _, v := range s {
		if shouldKeep(v) {
			result = append(result, v)
		}
	}
	if clear { // avoid memory leaking
		temp := s[len(result):]
		for i := range temp {
			temp[i] = t0 // t0 is a zero value literal of T.
		}
	}
	return result
}

Insert A Slice Into Another One

Assume the insertion position is a legal index i and the slice to be inserted is called elements.
// One-line implementation:
s = append(s[:i], append(elements, s[i:]...)...)

// A more efficient but more verbose way:
if cap(s)-len(s) >= len(elements) {
	s = s[:len(s)+len(elements)]
	copy(s[i+len(elements):], s[i:])
	copy(s[i:], elements)
} else {
	x := make([]T, 0, len(elements)+len(s))
	x = append(x, s[:i]...)
	x = append(x, elements...)
	x = append(x, s[i:]...)
	s = x
}

// Push:
s = append(s, elements...)

// Unshift:
s = append(elements, s...)

Insert Several Slice Elements

Inserting several elements is similar to inserting a slice. We can construct a slice with a slice composite literal with the elements to be inserted, then use the above ways to insert the new slice.

Special Deletions And Insertions: Push Front/Back, Pop Front/Back

Assume the pushed or popped element is e and slice s has at least one element.
// Pop front (shift):
s, e = s[1:], s[0]
// Pop back:
s, e = s[:len(s)-1], s[len(s)-1]
// Push front:
s = append([]T{e}, s...)
// Push back
s = append(s, e)

About The Above Examples For More Slice Operations

In reality, the needs are varied. For some cases, it is possible none of the above ways shown in the above examples are the most efficient way. And sometimes, the above ways may not satisfy some specific requirements in parctice. So, please learn and apply elastically. This may be the reason why Go doesn't support the more oprations introduced above in the built-in way.

Use Maps To Simulate Sets

Go doesn't support built-in set types. However, it is easy to use a map type to simulate a set type. In practice, we often use the map type map[K]struct{} to simulate a set type. The size of the map element type struct{} is zero, elements of values of such map types don't occupy memory space.

Container Related Opetaions Are Not Synchronized Internally

Please note that, all container operations are not synchronized internally. Without making using of any data synchronization technique, it is okay for multiple goroutines to read a container concurrently, but it is not okay for multiple goroutines to maniluplate a container concurrently and at least one goroutine modifies the container. The latter case will cause data races, even make goroutines panic. We must synchronize the container operations manually. Please read the articles on data synchrinizations for details.

The Go 101 project is hosted on both github and gitlab. Welcome to improve Go 101 articles by submitting corrections for all kinds of mistakes, such as typos, grammar errors, wording inaccuracies, description flaws and code bugs.