Go Slices: Difference between revisions

From NovaOrdis Knowledge Base
Jump to navigation Jump to search
 
(285 intermediate revisions by the same user not shown)
Line 1: Line 1:
=External=
=External=
* https://go.dev/ref/spec#Slice_types
* https://go.dev/ref/spec#Slice_types
* https://blog.golang.org/slices
* https://go.dev/blog/slices
* https://github.com/golang/go/wiki/SliceTricks
* https://go.dev/wiki/SliceTricks


=Internal=
=Internal=
* [[Go_Language#Slices|Go Language]]
* [[Go_Language#Slice|Go Language]]
* [[Go Arrays]]
* [[Go_Arrays#Overview|Go Arrays]]
* [[Go_Package_slices|Package <tt>slices</tt>]]
* [[Go_Strings|Strings]]
* [[Go_Maps|Go Maps]]


=Overview=
=Overview=
A slice is a contiguous "window" on an underlying [[Go_Arrays#Overview|array]]. In Go, slices are used instead of arrays, and they are some times referred to as dynamic arrays.
A slice is a '''descriptor''', or a '''header''' that defines a contiguous segment of an underlying [[Go_Arrays#Overview|array]], stored separately from the slice variable. A slice is not an array, a slice describes a piece of an array, and it is the idiomatic Go way of manipulating sequential, indexed data. Given that declaring a slice will create the backing array if no array is explicitly provided, you can almost always use a slice instead of an array. The slice provides access to a numbered sequence of elements from that array. A slice type denotes the set of all slices of arrays of its element type.  


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 contains a [[#Pointer|pointer to the underlying array]], a [[#Length|length]] and a [[#Capacity|capacity]]. More details are available in the [[#Structure|Structure]] section. A slice, once initialized, is always associated with the underlying array that holds its elements. The slice shares storage with its array, and other slices of the same array. A distinct slice that shares the underlying array with an original slice can be created by slicing the original slice with a [[Go_Slice_Expressions#Overview|slice expression]].


A slice is a three-field data structure that contains the metadata needed to manipulate the underlying array:
Once the slice is initialized and the association with the underlying array is established, the association never changes, and the underlying array never changes, it cannot be grown or shrunk. However, a slice can be grown beyond the limits imposed by the original array, by allocating a new array under the covers, creating a new slice, copying elements across and using the second slice as the first slice. For more details, see <code>[[#append()|append()]]</code> below.
* <span id='Pointer'></span>the '''pointer''', which indicates the start of the slice in the underlying array, as the index of an array's element.
* <span id='Length'></span>the '''length''' is the number of elements in the slice. The length is read with function <code>[[#len.28.29|len()]]</code>.
* <span id='Capacity'></span>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|pointer]]. The capacity is read with function <code>[[#cap.28.29|cap()]]</code>. 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 with <code>[[#append.28.29|append()]]</code>.


[[Image:Slice.png]]
Slices [[Go_Slices#Slices_and_Pass-by-Value|are passed by value]], like an other Go variable, but when that happens, the slice argument of a function and the internal variable copy of the slice still share the underlying array.


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.
The Go documentation used to refer to slices as [[Go_Language#Reference_Type|reference types]], but not anymore. The "reference type" terminology was removed from Go documentation. The slices are sometimes referred to as dynamic arrays.


Lexically, the slice type is a [[Go_Language#Reference_Type|reference type]].
=Structure=


<span id='Slice_Performance'></span>Unlike arrays, which [[Go_Arrays#Arrays_are_Values|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.
Under the covers, a slice is a data structure that contains a [[#Pointer|pointer]], the [[#Length|length]] of the slice and the [[#Capacity|capacity]] of the slice. These elements are referred to as contents, fields or components of the slice. You can think of it as being built like this:
 
=Declaration=
A slice may be declared with the following notation:
<syntaxhighlight lang='go'>
<syntaxhighlight lang='go'>
var <var-name> []<type>
type sliceHeader struct {
    fistElement *T // pointer to the underlying array element that is the first element of the slice
    length      int
    capacity    int
}
</syntaxhighlight>
</syntaxhighlight>
==Pointer==
The pointer field contains a pointer to the underlying array element that represents the first element of the slice. The slice indexing is zero-based, so technically this would be slice element with the index 0.
==Length==
A slice's '''length''' represents the number of elements in the slice. The length of a slice is reported by the <code>[[#len()|len()]]</code> built-in function.
If a slice declares that it has length 3, that means it has three elements accessible as <code>s[0]</code>, <code>s[1]</code> and <code>s[2]</code>. Any attempt to access an element with an index lower than 0 or higher then 2 (<code>len - 1</code>), for read or write, results in a panic, even if the slice has sufficient [[#Capacity|capacity]]:
<font size=-1.5>
panic: runtime error: index out of range [3] with length 3
</font>
"Length" and "size" can be used interchangeably.
<font color=darkkhaki>'''Changing the Length'''. Do we ever change the length of a slice, or simply we create another slice, which points to the same underlying array, with a different length? For example, [[Go_Slice_Expressions#Overview|reslicing]] seems to always create a new slice. <code>[[#append()|append()]]</code> updates (possibly) the copy of the slice passed as argument and returns that copy, so the original argument is never changed. It probably does not make sense to talk about changing the length of the slice. We're working with disposable copies, so it does not help to think about changing the size "in-place".</font>
==Capacity==
The capacity is the maximum value the [[#Length|length]] can reach, and reflects how much space the underlying array actually has. The capacity is equal to the length of the underlying array, minus the index in the array of the first element of the slice. The capacity of a slice is reported by the <code>[[#cap()|cap()]]</code> built-in function.
Note that once an array is instantiated, it cannot change its size.
An existing "logical" slice can grow beyond the capacity of the underlying array by creating a new slice with a larger underlying array with <code>[[#append.28.29|append()]]</code> function.
=Slices and Pass-by-Value=
Go uses [[Go_Functions#Pass-by-value_vs._pass-by-pointer_vs._pass-by-reference|pass-by-value]], so when a slice argument is passed to a function, the internal fields are copied across on the function stack, including the pointer to the underlying array. Therefore, the underlying array data structure is intrinsically shared, even in case of pass-by-value. Both the original slice header and the copy of the header passed to the function describe the same array. If the function changes the underlying array via its copy of the slice, the modified elements can be seen outside the function through the original slice variable.
However, if the function modifies the elements of the slice, like the length, or the pointer to the array element, those changes naturally do not propagate outside of the function. Only the changes to the underlying array do.
The slice variables are small, so their content can be copied fast. The fact that they include a pointer to the underlying array prevents copying the array, which has important performance implications, especially if the underlying array is large.
Note that slices are not [[Variables,_Parameters,_Arguments#Reference_Variable|reference variables]], as Go does not support reference variables, so no two different slice variables may point to the same slice instance.
=Pointers to Slices=
It is idiomatic to use a pointer receiver for a method that modifies a slice.
=<tt>nil</tt> and Empty Slice=
There are both [[#nil|<code>nil</code>]] and [[#Empty|empty]] slices, and the difference between them is quite significant.
==<span id='nil'></span><tt>nil</tt> Slice==
A <code>nil</code> slice is a slice that has both the length and the capacity equal with zero, and the pointer to the first slice element, stored in the underlying array, equal to <code>nil</code>, which indicates that there is no underlying array. Using the same imaginary <code>struct</code> that helped explain slices in the [[#Structure|Structure]] section, a <code>nil</code> slice would be represented like this:
<syntaxhighlight lang='go'>
<syntaxhighlight lang='go'>
var s []int
type sliceHeader struct {
    fistElement nil
    length      0
    capacity    0
}
</syntaxhighlight>
</syntaxhighlight>
This declaration is similar to an array's except the length is not specified. Note that this declaration will create a <code>nil</code> slice. See [[#Empty_Slice_and_nil_Slice|Empty Slice and <tt>nil</tt> Slice]] below.
The key element that makes a slice <code>nil</code> is that the first slice element is <code>nil</code>, so the underlying array does not exist. There could be zero-length, zero-capacity slices that have an underlying array, like the one shown [[#%22Almost%22_nil_Slices|here]] that are not <code>nil</code> slices.
 
A <code>nil</code> slice can be instantiated with the <code>[[Go_Slices#new()|new()]]</code> built-in function.


A slice may be declared on a previously declared array. The slice is declared using the following brackets and colon notation:
<code>append()</code> works with <code>nil</code> slices, <code>nil</code> slices can be safely appended to:
<syntaxhighlight lang='go'>
<syntaxhighlight lang='go'>
<array-variable-name>[<index-of-the-first-array-element-in-slice>:<index-of-the-first-array-element-outside-slice>]
var s []int
s = append(s, 1)
</syntaxhighlight>
</syntaxhighlight>


===Test <tt>nil</tt> Slice===
A slice can be tested that it is <code>nil</code> with:
<syntaxhighlight lang='go'>
<syntaxhighlight lang='go'>
colors := [...]string{"red", "blue", "green", "yellow", "black", "white", "purple"}
s == nil
s := colors[0:3]
</syntaxhighlight>
</syntaxhighlight>
which will return <code>true</code>. Note that the pointer to the slice is a non-<code>nil</code> pointer, which points to the handle containing the zero values. However, <code>%p</code> will render "0x0".


A slice may be declared, using a [[#Literal|slice literal]] so it creates the underlying array. When a slice is declared with a literal (see [[#Literals|Literals]] below for an example), the slice points to the start of the newly created array, and its [[#Length|length]] is equal with its [[#Capacity|capacity]].
==="Almost" <tt>nil</tt> Slices===
Slices can be built to have zero length and zero capacity but still be associated with an underlying array, and those slices are '''not''' <code>nil</code>.  


=Literals=
For example:
The following declaration initializes a slice. We know it's a slice and not an array because the type declaration uses <code>[]</code> (undetermined length) and not <code>[...]</code> or <code>[7]</code>, which, because of their specific length or the <code>...</code> token, signal that they represent an array.
<syntaxhighlight lang='go'>
<syntaxhighlight lang='go'>
s := []string{"red", "blue", "green"}
var aa [0]int
println(len(s)) // prints 3
ii := aa[0:0] // zero-length, zero-capacity slice, but not a nil slice
println(cap(s)) // prints 3
</syntaxhighlight>
</syntaxhighlight>
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:
instantiates a zero-length, zero-capacity slice that is still associated with a zero-length underlying array, so it is not a <code>nil</code> slice. The <code>s == nil</code> test returns <code>false</code>.
 
The same result can be obtained with:
<syntaxhighlight lang='go'>
<syntaxhighlight lang='go'>
s := []int{9: 0}
ii := make([]int, 0) // zero-length, zero-capacity slice, but not a nil slice
</syntaxhighlight>
</syntaxhighlight>


=Initialization=
==<span id='Empty'></span>Empty Slice==
Slices can be initialized using an underlying array:
An empty slice is a slice with 0 length.  
<syntaxhighlight lang='go'>
 
a := [...]int{7, 17, 37, 47, 57}
An empty slice can grow, assuming it has non-zero capacity.
b := a[1:4]
</syntaxhighlight>
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 <code>high - low</code>. However, the capacity of the new slice will be determined by the boundary of the shared underlying array, so it will be <code>len(underlying_array) - low</code>. The same result is yielded by <code>cap(a) - low</code>.


[[Image:SliceExpression.png]]
=Declaration and Initialization=


Slices can be initialized with a [[#Literal|slice literal]]:
A slice can be declared with the [[Go_Variables#Long_Variable_Declaration|long variable declaration]].
<syntaxhighlight lang='go'>
<syntaxhighlight lang='go'>
s2 := []int{7, 17, 37}
var <sliceVarName> []<type>
</syntaxhighlight>
</syntaxhighlight>
<span id='Slice_Initialization_with_make'></span>Slices can also be initialized using the [[Go_Functions#Built-in_Functions|built-in function]] <code>[[Go Built-In Function make#Overview|make()]]</code>:
<span id='Nil_Slice_Idiomatic'></span><syntaxhighlight lang='go'>
<syntaxhighlight lang='go'>
var ii []int
make([]<element-type>, <length>, [capacity])
</syntaxhighlight>
</syntaxhighlight>
This declaration creates a [[#nil|<code>nil</code> slice]], and it is the idiomatic way to initialize nil slices.
Inside functions, the [[Go_Variables#Short_Variable_Declaration|short variable declaration]] can be used with <code>[[#make|make()]]</code> or a [[#Composite_Literal|composite literal]].
==<span id='make'></span>Initialization with <tt>make()</tt>==
<code>make()</code> allocates a new underlying array, and creates a new slice header to describe it, all at once.
There are two forms. In both cases, the elements of the slice are initialized with the zero value for the slice type.
One form specifies the [[#Length|length]] and the [[#Capacity|capacity]] of the slice:
<syntaxhighlight lang='go'>
<syntaxhighlight lang='go'>
s := make([]int, 3) // create an int slice of length (and capacity) 3
// Create a slice with length 3 and capacity 5.
s2 := make([]int, 3, 5) // create an int slice of length 3 and capacity 5
// All elements are initialized with the zero value for the slice type.
ii := make([]int, 3, 5)  
</syntaxhighlight>
</syntaxhighlight>
<code>make()</code> returns the slice instance, not a pointer. It is not allowed to create a slice with a capacity smaller than its length, such a situation will be handled as compile-time error.


=Empty Slice and <tt>nil</tt> Slice=
A simpler form specifies only the [[#Length|length]], with the implication that the underlying array will be created to accommodate the required length, and not more. In other words, the length will be equal with the capacity.
Declaring a slice without initializing it creates a <code>nil</code> slice. The slice variable is initialized with <code>nil</code>. This is common behavior for [[Go_Language#Reference_Type|reference types]].
<syntaxhighlight lang='go'>
<syntaxhighlight lang='go'>
var s []int
// Create a slice with length equal to its capacity.
fmt.Printf("%p\n", s) // will display 0x0
// All elements are initialized with the zero value for the slice type.
ii := make([]int, 3)  
</syntaxhighlight>
</syntaxhighlight>
If the initialization statement invokes <code>make()</code>, it will create an empty slice:
 
Note that unlike <code>[[#new()|new()]]</code>, <code>make()</code> returns the slice, not a pointer.
 
==<span id='Composite_Literal'></span>Initialization with a Composite Literal==
Initialization with a composite literal is idiomatic, [[#Nil_Slice_Idiomatic|unless we need a nil slice]], and also has the advantage that it allows to specify the slice content, in-line. Both the length and the capacity of the resulted slice are set to the length of the composite literal. The composite literal initialization creates the underlying array.
 
[[Go_Variables#Variable_Initialization_with_Type_Inference_in_Long_Declaration|Long variable declaration and initialization with type inference]]:
<syntaxhighlight lang='go'>
<syntaxhighlight lang='go'>
s := make([]int, 0)
var ii = []int{1, 2, 3} // len 3, cap 3
fmt.Printf("%p\n", s) // will display something else than 0x0
</syntaxhighlight>
</syntaxhighlight>
An empty slice points to a zero length array. The slice length is initialized to 0 and the capacity is initialized to 0. Regardless of whether they are applied to a <code>nil</code> or an empty slice, the built-in functions <code>append()</code>, <code>len()</code> and <code>cap()</code> work consistently.


=Operators=
[[Go_Variables#Short_Variable_Declaration|Short variable declaration]]:
==Indexing Operator <tt>[]</tt>==
Slice elements can be read and written with the indexing operator <code>[[Go_Language#Indexing_Operator|[]]]</code>.
<syntaxhighlight lang='go'>
<syntaxhighlight lang='go'>
colors := [...]string{"red", "blue", "green", "yellow"}
ii := []int{1, 2, 3} // len 3, cap 3
s := colors[0:3]
println(s[1]) // prints "blue"
s[1] = "fuchsia"
println(s[1]) // prints "fuchsia"
</syntaxhighlight>
</syntaxhighlight>
A slice can only access indexes up to its length, an attempt to access an element beyond the slice length will cause a runtime error. If the slice 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 <code>[[#append.28.29|append()]]</code>) and an attempt to access them via the index operator will also cause a runtime error.


=Functions=
===Sparse Initialization===
==<span id='Slice_Length'></span><tt>len()</tt>==
<code>len()</code> is a [[Go_Functions#len.28.29|built-in function]] that returns the [[#Length|length]] of a slice.


==<span id='Slice_Capacity'></span><tt>cap()</tt>==
A special form of composite literal can be used when we want to initialize only '''some''' of the slice elements with constant value. <code><index>:<value>,...</code> format can be used. The last <code><index>:<value></code> pair specifies the last element of the slice, thus determining both the length and the capacity. The elements that are not explicitly provided are initialized with the zero-value for the type.
<code>cap()</code> is a [[Go_Functions#cap.28.29|built-in function]] that returns the [[#Capacity|capacity]] of a slice.


==<tt>make()</tt>==
The following example specifies a slice whose elements with the index 1, 3 and 5 are 10, 30 and 50 respectively. Because the element with the index 5 is the last element, the slice has a length of 6, and the capacity equals to the length:
<code>make()</code> is a [[Go_Functions#cap.28.29|built-in function]] that can be used to make new slices. See  [[#Slice_Initialization_with_make|Initialization]] above.
==<tt>append()</tt>==
<code>append()</code> is a [[Go_Functions#cap.28.29|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.
<syntaxhighlight lang='go'>
<syntaxhighlight lang='go'>
//
ii := []int{1:10, 3:30, 5:50} // length 6, capacity 6, [0 10 0 30 0 50]
// create a zero-length slice with capacity 3
</syntaxhighlight>
//
s := make([]int, 0, 3)


//
==Initialization by Slicing the Underlying Array==
// create a new slice with append()
Given an array variable, a slice of it can be declared by using the [[Go_Slice_Expressions#Overview|slice expression]].
// this append() invocation does not increase the capacity
<syntaxhighlight lang='go'>
//
var aa [100]int // zero-value array declaration
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
var ss = aa[10:20] // slice declaration with slice expression
fmt.Printf("source slice len: %d, cap: %d\n", len(s), cap(s)) // will print 0, 3
</syntaxhighlight>
fmt.Printf("new slice len: %d, cap: %d, elements: %d\n", len(s2), cap(s2), s2[0]) // will print 1, 3, 11
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 <code>high - low</code>. However, the capacity of the new slice will be determined by the boundary of the underlying array, so it will be <code>len(underlying_array) - low</code>.


//
=Operators=
// create a new slice with append()
==Indexing Operator <tt>[]</tt>==
// 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


Individual slice elements can be read and written with the indexing operator <code>[[Go_Language#Indexing_Operator|[]]]</code>.
<syntaxhighlight lang='go'>
ii := []int{0, 1, 2}
println(ii[0]) // prints 0
ii[0] = 10
println(ii[0]) // prints 10
</syntaxhighlight>
</syntaxhighlight>
A slice can only access indexes up to its length, an attempt to access an element beyond the slice length will cause a runtime panic. Even if the slice capacity exceeds its length, the elements associated to the capacity that do not belong to the slice still cannot be accessed via the index operator, they're only available for growth and an attempt to access them via the index operator will also cause a runtime panic.


=TO DISTRIBUTE=
Negative indexes are not supported, attempting to access a slice with a negative index generates a runtime panic.
===<tt>append()</tt>===


<tt>append()</tt> adds element at the end of the slice, increasing its length, and, if necessary, the capacity:
==Slice Expressions==
{{Internal|Go_Slice_Expressions#Overview|Slice Expressions}}


<pre>
=Slice Functions=
<new_slice_identifier> := append(<old_slice_identifier>, <new_element>)
==<tt>len()</tt>==
</pre>
<code>len()</code> is a [[Go_Functions#len.28.29|built-in function]] that returns the slice [[#Length|length]]. Calling <code>len()</code> on a [[#nil|<code>nil</code> slice]] is legal and returns 0.


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|"slices share memory" note]].
==<tt>cap()</tt>==
<code>cap()</code> is a [[Go_Functions#cap.28.29|built-in function]] that returns the slice [[#Capacity|capacity]]. Calling <code>cap()</code> on a [[#nil|<code>nil</code> slice]] is legal and returns 0.


<blockquote style="background-color: Gold; border: solid thin Goldenrod;">
==<tt>copy()</tt>==
:The function always creates a new slice instance, don't expect that a lateral effect will change your input slice.<br>
<code>copy()</code> accepts two arguments, both slices. It copies the data from the right-hand argument into the left-hand argument. While copying, it pays attention the the length of both arguments, and it only copies what it can without panicking, which is the minimum of the lengths of those two slices.
</blockquote>
<syntaxhighlight lang='go'>
src := []int{10, 20, 30, 40, 50}
dest := make([]int, 3)
copy(dest, src) // no panic, only the first three elements will be copied across
</syntaxhighlight>


Example:
<code>copy()</code> can be used to copy elements around within the same slice, even if the ranges overlap. It can be used for shifting sections of the slice left or right:
 
<syntaxhighlight lang='go'>
<pre>
s := make([]int, 5, 10)
s := make([]int, 0) // the length of s is 0
for i := 0; i < 5; i++ {
 
s[i] = i + 1
s2 := append(s, 1) // the length of s stays 0
                  // but the length of s2 (a new slice) is 1
</pre>
 
====<tt>append()</tt> as variadic function====
 
<tt>append()</tt> is a variadic function, it accepts multiple elements to be appended to the slice:
 
<pre>
s2 := append(s, 1, 2, 3, 4, 5)
</pre>
 
When we want to pass the variadic arguments of an enclosing function, we need to use this syntax:
 
<pre>
func enclosing(args ...int) {
  ...
  s = append(s, args ...)
}
}
</pre>
// s is [1 2 3 4 5]


Note the use of ellipsis (...) when passing the <tt>args...</tt> variadic argument to <tt>append()</tt>. More on variadic functions is available here [[Go_Concepts_-_Functions#Varidic_Functions|Variadic Functions]].
// shift elements starting with 2 right one position
copy(s[3:], s[2:])
// s is [1 2 3 3 4]
</syntaxhighlight>


===<tt>copy()</tt>===
==<tt>append()</tt>==


<pre>
The <code>append()</code> built-in function appends elements to the end of a slice. If the target has sufficient capacity, it just updates its length to accommodate the new elements. If it does not, a new underlying array is allocated. <code>append()</code> returns the updated slice.
copy(dest_slice, source_slice)
</pre>


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.
<code>append()</code> works with [[#nil|<code>nil</code> slices]], appending to a <code>nil</code> slice just allocates a new slice.


===Slice Expression===
Since the argument of the <code>append()</code> function is passed by value, the changes occur on a copy of the original slice, so to make the original slice reflect the changes, it is necessary to store the result of the <code>append()</code>, often in the variable holding the original slice itself, with this idiomatic pattern:


See [[#Short_Declaration_with_a_Slice_Expression|Short Declaration with a Slice Expression]] above.
<syntaxhighlight lang='go'>
ii := []int{1, 2}
ii = append(ii, 3)
</syntaxhighlight>
In fact, the compiler won't let you call append without saving the result.


===Iterating over Slices===
===<tt>append()</tt> as Variadic Function===
<blockquote style="background-color: #f9f9f9; border: solid thin lightgrey;">
<code>append()</code> is a variadic function, it accepts multiple elements to be appended to the slice:
:[[Go Keyword range|<tt>range</tt> Keyword]]
</blockquote>


==Passing a Slice's Elements to a Variadic Function==
<syntaxhighlight lang='go'>
ii2 := append(ii, 1, 2, 3, 4, 5)
</syntaxhighlight>


<blockquote style="background-color: #f9f9f9; border: solid thin lightgrey;">
===Appending an Entire Slice===
:[[Go_Concepts_-_Functions#Varidic_Functions|Variadic Functions]]
</blockquote>


==Multidimensional Slices==
To append a slice of the same type, use this syntax:
<syntaxhighlight lang='go'>
ii := []int{1, 2}
ii2 := []int{3, 4}
ii = append(ii, ii2...)
</syntaxhighlight>


<font color=red>'''TODO''' Go in Action page 100.</font>
Idiom to insert an element on the first position in a slice:
<syntaxhighlight lang='go'>
headers := []string{"B", "C"}
headers = append([]string{"A"}, headers...)
</syntaxhighlight>
The result with be: ["A", "B", "C"].


=TO DISTRIBUTE=
===Appending an String to a Byte Slice===
<font color=darkkhaki>
==Overlapping Slices==
<span id="Slices_Share_Memory"></span>{{Note|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 [[Go_Slices#Three-Index_Slice_Expression|Three-index Slice Expression]] below.}}


Special forms of the "slice" expression:
As a special case, it is legal to append a string to a byte slice (notice the ellipsis):


* <tt>a[i:]</tt> is equivalent with <tt>a[i:len(a)]</tt>
<syntaxhighlight lang='go'>
* <tt>a[:i]</tt> is equivalent with <tt>a[0:i]</tt>
s := []byte("hello ")
* <tt>a[:]</tt> is equivalent with <tt>a[0:len(a)]</tt>
s = append(s, "world"...)
</syntaxhighlight>


===Three-Index Slice Expression===
==<tt>make()</tt>==
<code>make()</code> is a [[Go_Functions#make()|built-in function]] that creates ready-to-use slices. See [[#Initialization_with_make()|Initialization with <tt>make()</tt>]] above.


The third index specified with the slice expression ''restricts the capacity'' of the newly created slice.
==<tt>new()</tt>==
<code>new()</code> will initialize a [[Go_Slices#nil_Slice|<tt>nil</tt> Slice]], but it will return the pointer to the slice, instead of the value.


<pre>
A slice returned by <code>new()</code> can be positively tested as <code>nil</code> (careful to test the slice, not the pointer to it):
new_slice_identifier := old_slice_identifier[low:high:high_for_capacity]
<syntaxhighlight lang='go'>
</pre>
s := new([]int)
fmt.Println(*s == nil) // true
</syntaxhighlight>


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.
=The <tt>slices</tt> Package=
{{Internal|Go_Package_slices#Overview|The <tt>slices</tt> Package}}
=Iterating over a Slice=
Use the <code>[[Go_Keyword_range|range]]</code> [[Go_Language#Keywords|keyword]]. <code>[[Go_Keyword_range|range]]</code> allows to iterate by indices, values or both.
Iterate by indices:
<syntaxhighlight lang='go'>
for i := range ss {
  fmt.Printf("index: %d\n", i)
}
</syntaxhighlight>
Iterate by values:
<syntaxhighlight lang='go'>
for _, s := range ss {
  fmt.Printf("element value: %s\n", s)
}
</syntaxhighlight>
Iterate by both indices and values:
<syntaxhighlight lang='go'>
for i, s := range ss {
  fmt.Printf("index: %d, element value: %s\n", i, s)
}
</syntaxhighlight>
==Iterating over a <tt>nil</tt> Slice==


Example:
<code>range</code> applied to a <code>nil</code> slice does not panic and iterates zero times.


<pre>
=Sorting Slices=
a := []string {"a", "b", "c", "d", "e"} // creates a slice with length 5 and capacity 5
There are sorting functions provided by the  <code>[[Go Package slices#Sorting|slices]]</code> package. The <code>[[Go Package sort#Overview|sort]]</code> package also provides sorting, but the <code>sort</code> documentation mentions that the <code>slice</code> sorting functions run faster.
b := a[1:2:3] // creates a slice with length 1, containing 'b", and the capacity 2
</pre>


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 <tt>b[0]="x"</tt> will cause a[1] to become "x".
{{Internal|Go_Package_slices#Sorting|Sorting with the <tt>slices</tt> Package}}


The three-index slice expression is useful to create slices whose capacity is the same as the length, so the first <tt>append()</tt> operation creates a new underlying array, thus completely detaching the slices - which is a good thing. See [[#Slices_Share_Memory|"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:
=String as Slices=
 
{{Internal|Go_Strings#Strings_as_Slices|Strings as Slices}}
<pre>
=Multidimensional Slices=
b := a[low, high, high]
<font color=darkkhaki>
</pre>
TODO:
 
* https://go.dev/doc/effective_go#two_dimensional_slices
Example:
* Go in Action page 100
</font>


<pre>
=Overlapping Slices=
b := a[2, 5, 5]
<span id="Slices_Share_Memory"></span>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 [[Go_Slice_Expressions#Three-Index_Slice_Expression|Three-index Slice Expression]].
</pre>
==Further Process==
* https://blog.golang.org/slices
* 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

Latest revision as of 00:04, 31 October 2024

External

Internal

Overview

A slice is a descriptor, or a header that defines a contiguous segment of an underlying array, stored separately from the slice variable. A slice is not an array, a slice describes a piece of an array, and it is the idiomatic Go way of manipulating sequential, indexed data. Given that declaring a slice will create the backing array if no array is explicitly provided, you can almost always use a slice instead of an array. The slice provides access to a numbered sequence of elements from that array. A slice type denotes the set of all slices of arrays of its element type.

A slice contains a pointer to the underlying array, a length and a capacity. More details are available in the Structure section. A slice, once initialized, is always associated with the underlying array that holds its elements. The slice shares storage with its array, and other slices of the same array. A distinct slice that shares the underlying array with an original slice can be created by slicing the original slice with a slice expression.

Once the slice is initialized and the association with the underlying array is established, the association never changes, and the underlying array never changes, it cannot be grown or shrunk. However, a slice can be grown beyond the limits imposed by the original array, by allocating a new array under the covers, creating a new slice, copying elements across and using the second slice as the first slice. For more details, see append() below.

Slices are passed by value, like an other Go variable, but when that happens, the slice argument of a function and the internal variable copy of the slice still share the underlying array.

The Go documentation used to refer to slices as reference types, but not anymore. The "reference type" terminology was removed from Go documentation. The slices are sometimes referred to as dynamic arrays.

Structure

Under the covers, a slice is a data structure that contains a pointer, the length of the slice and the capacity of the slice. These elements are referred to as contents, fields or components of the slice. You can think of it as being built like this:

type sliceHeader struct {
    fistElement *T // pointer to the underlying array element that is the first element of the slice
    length      int
    capacity    int
}

Pointer

The pointer field contains a pointer to the underlying array element that represents the first element of the slice. The slice indexing is zero-based, so technically this would be slice element with the index 0.

Length

A slice's length represents the number of elements in the slice. The length of a slice is reported by the len() built-in function.

If a slice declares that it has length 3, that means it has three elements accessible as s[0], s[1] and s[2]. Any attempt to access an element with an index lower than 0 or higher then 2 (len - 1), for read or write, results in a panic, even if the slice has sufficient capacity:

panic: runtime error: index out of range [3] with length 3

"Length" and "size" can be used interchangeably.

Changing the Length. Do we ever change the length of a slice, or simply we create another slice, which points to the same underlying array, with a different length? For example, reslicing seems to always create a new slice. append() updates (possibly) the copy of the slice passed as argument and returns that copy, so the original argument is never changed. It probably does not make sense to talk about changing the length of the slice. We're working with disposable copies, so it does not help to think about changing the size "in-place".

Capacity

The capacity is the maximum value the length can reach, and reflects how much space the underlying array actually has. The capacity is equal to the length of the underlying array, minus the index in the array of the first element of the slice. The capacity of a slice is reported by the cap() built-in function.

Note that once an array is instantiated, it cannot change its size.

An existing "logical" slice can grow beyond the capacity of the underlying array by creating a new slice with a larger underlying array with append() function.

Slices and Pass-by-Value

Go uses pass-by-value, so when a slice argument is passed to a function, the internal fields are copied across on the function stack, including the pointer to the underlying array. Therefore, the underlying array data structure is intrinsically shared, even in case of pass-by-value. Both the original slice header and the copy of the header passed to the function describe the same array. If the function changes the underlying array via its copy of the slice, the modified elements can be seen outside the function through the original slice variable.

However, if the function modifies the elements of the slice, like the length, or the pointer to the array element, those changes naturally do not propagate outside of the function. Only the changes to the underlying array do.

The slice variables are small, so their content can be copied fast. The fact that they include a pointer to the underlying array prevents copying the array, which has important performance implications, especially if the underlying array is large.

Note that slices are not reference variables, as Go does not support reference variables, so no two different slice variables may point to the same slice instance.

Pointers to Slices

It is idiomatic to use a pointer receiver for a method that modifies a slice.

nil and Empty Slice

There are both nil and empty slices, and the difference between them is quite significant.

nil Slice

A nil slice is a slice that has both the length and the capacity equal with zero, and the pointer to the first slice element, stored in the underlying array, equal to nil, which indicates that there is no underlying array. Using the same imaginary struct that helped explain slices in the Structure section, a nil slice would be represented like this:

type sliceHeader struct {
    fistElement nil
    length      0
    capacity    0
}

The key element that makes a slice nil is that the first slice element is nil, so the underlying array does not exist. There could be zero-length, zero-capacity slices that have an underlying array, like the one shown here that are not nil slices.

A nil slice can be instantiated with the new() built-in function.

append() works with nil slices, nil slices can be safely appended to:

var s []int
s = append(s, 1)

Test nil Slice

A slice can be tested that it is nil with:

s == nil

which will return true. Note that the pointer to the slice is a non-nil pointer, which points to the handle containing the zero values. However, %p will render "0x0".

"Almost" nil Slices

Slices can be built to have zero length and zero capacity but still be associated with an underlying array, and those slices are not nil.

For example:

var aa [0]int
ii := aa[0:0] // zero-length, zero-capacity slice, but not a nil slice

instantiates a zero-length, zero-capacity slice that is still associated with a zero-length underlying array, so it is not a nil slice. The s == nil test returns false.

The same result can be obtained with:

ii := make([]int, 0) // zero-length, zero-capacity slice, but not a nil slice

Empty Slice

An empty slice is a slice with 0 length.

An empty slice can grow, assuming it has non-zero capacity.

Declaration and Initialization

A slice can be declared with the long variable declaration.

var <sliceVarName> []<type>

var ii []int

This declaration creates a nil slice, and it is the idiomatic way to initialize nil slices.

Inside functions, the short variable declaration can be used with make() or a composite literal.

Initialization with make()

make() allocates a new underlying array, and creates a new slice header to describe it, all at once.

There are two forms. In both cases, the elements of the slice are initialized with the zero value for the slice type.

One form specifies the length and the capacity of the slice:

// Create a slice with length 3 and capacity 5.
// All elements are initialized with the zero value for the slice type.
ii := make([]int, 3, 5)

A simpler form specifies only the length, with the implication that the underlying array will be created to accommodate the required length, and not more. In other words, the length will be equal with the capacity.

// Create a slice with length equal to its capacity.
// All elements are initialized with the zero value for the slice type.
ii := make([]int, 3)

Note that unlike new(), make() returns the slice, not a pointer.

Initialization with a Composite Literal

Initialization with a composite literal is idiomatic, unless we need a nil slice, and also has the advantage that it allows to specify the slice content, in-line. Both the length and the capacity of the resulted slice are set to the length of the composite literal. The composite literal initialization creates the underlying array.

Long variable declaration and initialization with type inference:

var ii = []int{1, 2, 3} // len 3, cap 3

Short variable declaration:

ii := []int{1, 2, 3} // len 3, cap 3

Sparse Initialization

A special form of composite literal can be used when we want to initialize only some of the slice elements with constant value. <index>:<value>,... format can be used. The last <index>:<value> pair specifies the last element of the slice, thus determining both the length and the capacity. The elements that are not explicitly provided are initialized with the zero-value for the type.

The following example specifies a slice whose elements with the index 1, 3 and 5 are 10, 30 and 50 respectively. Because the element with the index 5 is the last element, the slice has a length of 6, and the capacity equals to the length:

ii := []int{1:10, 3:30, 5:50} // length 6, capacity 6, [0 10 0 30 0 50]

Initialization by Slicing the Underlying Array

Given an array variable, a slice of it can be declared by using the slice expression.

var aa [100]int // zero-value array declaration
var ss = aa[10:20] // slice declaration with slice expression

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 underlying array, so it will be len(underlying_array) - low.

Operators

Indexing Operator []

Individual slice elements can be read and written with the indexing operator [].

ii := []int{0, 1, 2}
println(ii[0]) // prints 0
ii[0] = 10
println(ii[0]) // prints 10

A slice can only access indexes up to its length, an attempt to access an element beyond the slice length will cause a runtime panic. Even if the slice capacity exceeds its length, the elements associated to the capacity that do not belong to the slice still cannot be accessed via the index operator, they're only available for growth and an attempt to access them via the index operator will also cause a runtime panic.

Negative indexes are not supported, attempting to access a slice with a negative index generates a runtime panic.

Slice Expressions

Slice Expressions

Slice Functions

len()

len() is a built-in function that returns the slice length. Calling len() on a nil slice is legal and returns 0.

cap()

cap() is a built-in function that returns the slice capacity. Calling cap() on a nil slice is legal and returns 0.

copy()

copy() accepts two arguments, both slices. It copies the data from the right-hand argument into the left-hand argument. While copying, it pays attention the the length of both arguments, and it only copies what it can without panicking, which is the minimum of the lengths of those two slices.

src := []int{10, 20, 30, 40, 50}
dest := make([]int, 3)
copy(dest, src) // no panic, only the first three elements will be copied across

copy() can be used to copy elements around within the same slice, even if the ranges overlap. It can be used for shifting sections of the slice left or right:

s := make([]int, 5, 10)
for i := 0; i < 5; i++ {
	s[i] = i + 1
}
// s is [1 2 3 4 5]

// shift elements starting with 2 right one position
copy(s[3:], s[2:])
// s is [1 2 3 3 4]

append()

The append() built-in function appends elements to the end of a slice. If the target has sufficient capacity, it just updates its length to accommodate the new elements. If it does not, a new underlying array is allocated. append() returns the updated slice.

append() works with nil slices, appending to a nil slice just allocates a new slice.

Since the argument of the append() function is passed by value, the changes occur on a copy of the original slice, so to make the original slice reflect the changes, it is necessary to store the result of the append(), often in the variable holding the original slice itself, with this idiomatic pattern:

ii := []int{1, 2}
ii = append(ii, 3)

In fact, the compiler won't let you call append without saving the result.

append() as Variadic Function

append() is a variadic function, it accepts multiple elements to be appended to the slice:

ii2 := append(ii, 1, 2, 3, 4, 5)

Appending an Entire Slice

To append a slice of the same type, use this syntax:

ii := []int{1, 2}
ii2 := []int{3, 4} 
ii = append(ii, ii2...)

Idiom to insert an element on the first position in a slice:

headers := []string{"B", "C"}
headers = append([]string{"A"}, headers...)

The result with be: ["A", "B", "C"].

Appending an String to a Byte Slice

As a special case, it is legal to append a string to a byte slice (notice the ellipsis):

s := []byte("hello ")
s = append(s, "world"...)

make()

make() is a built-in function that creates ready-to-use slices. See Initialization with make() above.

new()

new() will initialize a nil Slice, but it will return the pointer to the slice, instead of the value.

A slice returned by new() can be positively tested as nil (careful to test the slice, not the pointer to it):

s := new([]int)
fmt.Println(*s == nil) // true

The slices Package

The slices Package

Iterating over a Slice

Use the range keyword. range allows to iterate by indices, values or both. Iterate by indices:

for i := range ss {
  fmt.Printf("index: %d\n", i)
}

Iterate by values:

for _, s := range ss {
  fmt.Printf("element value: %s\n", s)
}

Iterate by both indices and values:

for i, s := range ss {
  fmt.Printf("index: %d, element value: %s\n", i, s)
}

Iterating over a nil Slice

range applied to a nil slice does not panic and iterates zero times.

Sorting Slices

There are sorting functions provided by the slices package. The sort package also provides sorting, but the sort documentation mentions that the slice sorting functions run faster.

Sorting with the slices Package

String as Slices

Strings as Slices

Multidimensional Slices

TODO:

Overlapping Slices

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.