Go Slices: Difference between revisions
Line 295: | Line 295: | ||
<font color=red>'''TODO''' Go in Action page 100.</font> | <font color=red>'''TODO''' Go in Action page 100.</font> | ||
=TO DISTRIBUTE= | |||
<font color=darkkhaki> | |||
==Further Process== | ==Further Process== | ||
* https://blog.golang.org/slices | * https://blog.golang.org/slices | ||
* https://github.com/golang/go/wiki/SliceTricks | * https://github.com/golang/go/wiki/SliceTricks | ||
* http://stackoverflow.com/questions/25551082/how-would-you-access-the-underlying-array-passed-to-a-function-expecting-an-empt | * http://stackoverflow.com/questions/25551082/how-would-you-access-the-underlying-array-passed-to-a-function-expecting-an-empt |
Revision as of 22:13, 24 August 2023
External
- https://go.dev/ref/spec#Slice_types
- https://blog.golang.org/slices
- https://github.com/golang/go/wiki/SliceTricks
Internal
Overview
A slice is a contiguous "window" on an underlying array. In Go, slices are used instead of arrays, and they are some times referred to as dynamic arrays.
A slice has variable length, up the total length of the underlying array. "Length" and "size" can be used interchangeably. Unlike in array's case, the size of the slice can change.
A slice is a three-field data structure that contains the metadata needed to manipulate the underlying array:
- 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 capacity is read with function
cap()
. An existing slice can increase up to the end of the array, but the slice can be expanded beyond the length of the underlying array by creating a slice with a new, longer underlying array withappend()
.
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.
Lexically, the slice type is a reference type.
Unlike arrays, which are values and thus problematic to pass as function arguments, slices are amenable to be passed as arguments. They footprint is very small, and being passed by value is efficient: only the slice metadata is copied, not the underlying array. Also, when passing slices we don't have to deal with pointers and complicated syntax.
Declaration
A slice may be declared with the following notation:
var <var-name> []<type>
var s []int
This declaration is similar to an array's except the length is not specified.
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 (see Literals below for an example), 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
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}
Initialization
Slices can be initialized using an underlying array:
a := [...]int{7, 17, 37, 47, 57}
b := a[1:4]
The "low" index is the index where the slice starts and "high" is the index where slice ends. The new slice will exclude 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
.
Slices can be initialized with a slice literal:
s2 := []int{7, 17, 37}
Slices can also be initialized using the built-in function make()
:
make([]<type>, <length>, [capacity])
s := make([]int, 3) // create an int slice of length (and capacity) 3
s2 := make([]int, 3, 5) // create an int slice of length 3 and capacity 5
Empty Slice and nil Slice
TODO: deplete #nil_Slices_and_Empty_Slices.
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.
make()
make()
is a built-in function that can be used to make new slices. See Initialization above.
append()
append()
is a built-in function that can be used to simultaneously 1) create a new slice by copying the content of an existing slice provided as argument 2) add elements at the end of the newly created slice and 3) expand the newly created slice capacity, if by adding the elements we fall beyond the source slice capacity.
//
// create a zero-length slice with capacity 3
//
s := make([]int, 0, 3)
//
// create a new slice with append()
// this append() invocation does not increase the capacity
//
s2 := append(s, 11) // create a new slice, that has a length of 1, capacity of 3 and the element on position 0 equal to 11
fmt.Printf("source slice len: %d, cap: %d\n", len(s), cap(s)) // will print 0, 3
fmt.Printf("new slice len: %d, cap: %d, elements: %d\n", len(s2), cap(s2), s2[0]) // will print 1, 3, 11
//
// create a new slice with append()
// this append() invocation will increase the capacity
//
s3 := append(s, 11, 22, 33, 44) // create a new slice, that has a length of 4 and larger capacity
fmt.Printf("source slice len: %d, cap: %d\n", len(s), cap(s)) // will print 0, 3
fmt.Printf("new slice len: %d, cap: %d, elements: %d, %d, %d, %d\n", len(s3), cap(s3), s3[0], s3[1], s3[2], s3[3]) // will print 4, 6, 11, 22, 33, 44
TO DISTRIBUTE
Note that after applying the slicing operation, the resulted slice shares a portion of the underlying array with the initial slice, so changing an element in one slice is reflected in the other. This is not necessarily a good thing, because those two slices are connected in a non-obvious way, and making a change in one slice causes a (non-obvious) change in the second slice, leading to hard to troubleshoot bugs. In order to avoid this behavior, restrict the capacity of the newly created slice with a third index. See Three-index Slice Expression below.
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.
TO DISTRIBUTE