Go Slices: Difference between revisions
Line 39: | Line 39: | ||
=Initialization= | =Initialization= | ||
See [[#Literals|Literals]] above. | |||
=Operators= | =Operators= |
Revision as of 20:20, 24 August 2023
External
Internal
Overview
A slice is a contiguous "window" on an underlying array. In Go, slices are used instead of arrays.
A slice has variable size, up the total size of the underlying array. Unlike in array's case, the size of the slice can change.
Every slice has three properties:
- the pointer, which indicates the start of the slice in the underlying array, as the index of an array's element.
- the length is the number of elements in the slice. The length is read with function
len()
. - the capacity is the maximum number of elements in the slice. The capacity is determined as the difference between the size of underlying array and the slice's pointer. The slice can be increased up to the end of the array. The capacity is read with function
cap()
.
Overlapping slices can refer to the same underlying array, and changing an element in a slice reflect in all other slices associated with the array.
Declaration
A slice may be declared on a previously declared array. The slice is declared using the following brackets and colon notation:
<array-variable-name>[<index-of-the-first-array-element-in-slice>:<index-of-the-first-array-element-outside-slice>]
colors := [...]string{"red", "blue", "green", "yellow", "black", "white", "purple"}
s := colors[0:3]
A slice may be declared, using a slice literal so it creates the underlying array. When a slice is declared with a literal, the slice points to the start of the newly created array, and its length is equal with its capacity.
Literals
The following declaration initializes a slice. We know it's a slice and not an array because the type declaration uses []
(undetermined length) and not [...]
or [7]
, which, because of their specific length or the ...
token, signal that they represent an array.
s := []string{"red", "blue", "green"}
println(len(s)) // prints 3
println(cap(s)) // prints 3
Initialization
See Literals above.
Operators
Indexing Operator []
Slice elements can be read and written with the indexing operator []
.
colors := [...]string{"red", "blue", "green", "yellow"}
s := colors[0:3]
println(s[1]) // prints "blue"
s[1] = "fuchsia"
println(s[1]) // prints "fuchsia"
Functions
len()
len()
is a built-in function that returns the length of a slice.
cap()
cap()
is a built-in function that returns the capacity of a slice.
TO DISTRIBUTE
External
- Go Specification - Appending and Copying Slices https://golang.org/ref/spec#Appending_and_copying_slices
- Arrays, slices (and strings): The mechanics of 'append' https://blog.golang.org/slices
- Slice tricks: https://github.com/golang/go/wiki/SliceTricks
Internal
Overview
A slice is a reference type that implements a dynamic array. Slices are indexable, and they have a variable length. They are always associated with an underlying array, and the slice length cannot be longer than the underlying array - but it can be shorter. The slice's capacity is equal to the length on the underlying array. The index operator does not work beyond the capacity of the slice, but the slice can be expanded beyond the length of the underlying array by creating a new, longer underlying array - and consequently a new slice - with append().
Lexically, a slice type is a reference type. The slice instances must be initialized before attempting to use into them, either by using the make() function or a slice literal, otherwise you will get a runtime error, because a zero value for a slice is nil. More on nil slices: nil Slices and Empty Slices.
Unlike arrays, which are values and problematic to pass as function arguments, slices can be easily be passed as values - their memory footprint is very small, and when passed as values, only the slices are copied, not the underlying array. Also, when passing slices we don't have to deal with pointers and complicated syntax.
Implementation Details
The slices are three-field data structures that contain the metadata needed to manipulate the underlying array.
Accessing the Slice's Underlying Array
TODO, process this: http://stackoverflow.com/questions/25551082/how-would-you-access-the-underlying-array-passed-to-a-function-expecting-an-empt
Declaration
Long Declaration
A slice declaration is similar to an array's except the length is not specified. The slice is created with a zero length if no literal is specified.
var s []int
A slice declaration and initialization with a slice literal:
var s []int = []int {1, 2, 3}
Short Declaration
Short Declaration with Literals
A short declaration using a slice literal. The declaration is similar to declaring an array, except that the brackets do not contain the length:
s := []int{1, 2, 3}
The above declaration sets the length and capacity to 3.
A different literal format sets the length and the capacity by initializing only the last element of the slice. The following declaration creates a slice that has a length and capacity of 10:
s := []int{9: 0}
Short Declaration with make()
A short declaration can also be performed using make(). make() takes the length and the capacity of the slice.
Short Declaration with a Slice Expression
The slices can also be created with the "slice" expression applied to an array:
<array_identifier>[<low>:<high>]
Example:
var a [5]int b := a[1:4]
The "low" index is the index where to start the slice and "high" is the index where to end the new slice: the new slice will not contain the element indicated by the "high" index. The length of the new slice is given by the formula high - low. However, the capacity of the new slice will be determined by the boundary of the shared underlying array, so it will be len(underlying_array) - low. The same result is yielded by cap(a) - low.
Special forms of the "slice" expression:
- a[i:] is equivalent with a[i:len(a)].
- a[:i] is equivalent with a[0:i]
- a[:] is equivalent with a[0:len(a)]
Three-Index Slice Expression
The third index specified with the slice expression restricts the capacity of the newly created slice.
new_slice_identifier := old_slice_identifier[low:high:high_for_capacity]
The "low" and "high" have the same semantics as in the case of the regular slice expression. "high_for_capacity" is the index where to end the underlying array of the slice: the underlying array of the new slice can use as "capacity" elements all the elements up to, but not including the "high_for_capacity" index.
Example:
a := []string {"a", "b", "c", "d", "e"} // creates a slice with length 5 and capacity 5 b := a[1:2:3] // creates a slice with length 1, containing 'b", and the capacity 2
Note that "high_for_capacity" must fall inside the underlying array backing "a", or, at most, must fall right outside the array (which means that the whole array will be used for capacity) otherwise we get "slice bounds out of range". The slices keep sharing the memory areas corresponding to their backing arrays, so b[0]="x" will cause a[1] to become "x".
The three-index slice expression is useful to create slices whose capacity is the same as the length, so the first append() operation creates a new underlying array, thus completely detaching the slices - which is a good thing. See "slices share memory" note for an explanation why. From a practical perspective, it is a good idea to always use the "three-index slice expression" where the "high" and "high_for_capacity" coincide:
b := a[low, high, high]
Example:
b := a[2, 5, 5]
nil Slices and Empty Slices
Declaring a slice without initializing it creates a nil slice - the slice variable is initialized with nil. This is common behavior for reference types:
var s []int
For a nil slice, fmt.Printf("%p", s) prints 0x0. The underlying data structures are initialized as follows: the array pointer is initialized to nil, the length is initialized to 0 and the capacity is initialized to 0.
A nil slice is different from an empty slice:
s := []int{}
s := make([]int, 0)
For an empty slice, fmt.Printf("%p", s) prints a pointer: 0x19d730. An empty slice points to a zero length array. The length is initialized to 0 and the capacity is initialized to 0.
Regardless of whether a nil or empty slice is used, the built-in functions append(), len() and cap() work the same.
Slice Operators and Functions
Indexing Operator
Indexing operator [] works in the same way as with arrays. The indexing operator can be used to access and change values of individual elements.
A slice an only access indexes up to its length, an attempt to access an element outside the slice's length will cause a runtime error. If the slice's capacity exceeds its length, the elements associated to the capacity that do not belong to the slice cannot be accessed via the index operator: they're only available for growth (see append()) and an attempt to access them via the index operator will also cause a runtime error.
Slice Length
len() returns the length of the slice.
Slice Capacity
cap()
returns the length of the underlying array.
make()
The make() function creates the slice and the underlying array.
<slice_identifier> := make([]<slice_element_type>, <slice_length>, [capacity])
s := make([]int, 5) // the length and capacity are equal s1 := make([]int, 5, 10)
It is not allowed to create a slice with a capacity smaller than its length, the compiler will throw an error.
Note that make() returns the slice instance, not a pointer.
append()
append() adds element at the end of the slice, increasing its length, and, if necessary, the capacity:
<new_slice_identifier> := append(<old_slice_identifier>, <new_element>)
The addition is operated by writing the values in the corresponding positions in the underlying array, unless the array is not long enough. In this case, a new array long enough to accommodate the new elements is created and the original array values are copied across. Be aware that as long as no new array is created, the slices share the underlying array, thus are prone to lateral effects. Also see "slices share memory" note.
- The function always creates a new slice instance, don't expect that a lateral effect will change your input slice.
Example:
s := make([]int, 0) // the length of s is 0 s2 := append(s, 1) // the length of s stays 0 // but the length of s2 (a new slice) is 1
append() as variadic function
append() is a variadic function, it accepts multiple elements to be appended to the slice:
s2 := append(s, 1, 2, 3, 4, 5)
When we want to pass the variadic arguments of an enclosing function, we need to use this syntax:
func enclosing(args ...int) { ... s = append(s, args ...) }
Note the use of ellipsis (...) when passing the args... variadic argument to append(). More on variadic functions is available here Variadic Functions.
copy()
copy(dest_slice, source_slice)
The function copies all the source elements over the corresponding elements in the destination, overwriting the destination. If the lengths of the slices are not the same, the shortest one will be used.
Slice Expression
See Short Declaration with a Slice Expression above.
Iterating over Slices
Passing a Slice's Elements to a Variadic Function
Multidimensional Slices
TODO Go in Action page 100.