I'm trying to learn Golang using "The Go Programming Language" and I've reached the section on slices. They make the comparison between arrays and slices in that two arrays can be compared with ==
where two slices can not. The text reads as the following:
"== operator for arrays of strings, it may be puzzling that slice
comparisons do not also work this way. There are two reasons why deep
equivalence is problematic. First, unlike array elements, the elements
of a slice are indirect, making it possible for a slice to contain
itself. Although there are ways to deal with such cases, none is
simple, efficient, and most importantly, obvious."
What is meant by it's possible for a slice to contain itself due to the elements being indirect?
Removing an Element from a Slice To remove an element, you must slice out the items before that element, slice out the items after that element, then append these two new slices together without the element that you wanted to remove.
Use append to increase the length and capacity of a slice The append operation is clever when growing the capacity of the underlying array. For example, the capacity is always doubled when the existing capacity of the slice is under 1,000 elements.
The capacity of a slice is the number of elements in the underlying array, counting from the first element in the slice. The length and capacity of a slice s can be obtained using the expressions len(s) and cap(s) . You can extend a slice's length by re-slicing it, provided it has sufficient capacity.
Besides a recursive type (such as type Foo []Foo
, see ANisus's answer) which is good for nothing besides demonstration, a slice may contain itself if for example the element type of the slice is interface{}
:
s := []interface{}{"one", nil}
s[1] = s
In this example the slice s
will have 2 interface values, the first "wrapping" a simple string "one"
, and another interface value wrapping the slice value itself. When an interface value is created, a copy of the value will be wrapped which in case of slices means a copy of the slice header/descriptor, which contains the pointer to the underlying array, so the copy will have the same pointer value pointing to the same underlying array. (For more details about the representation of interfaces, see The Laws of Reflection: The representation of an interface.)
If you were quickly on to print it:
fmt.Println(s)
You would get a fatal error, something like:
runtime: goroutine stack exceeds 250000000-byte limit
fatal error: stack overflow
Because fmt.Println()
tries to print the content recursively, and since the 2nd element is a slice pointing to the same array of the the slice being printed, it runs into an infinite loop.
Another way to see if it really is the slice itself:
s := []interface{}{"one", nil}
s[1] = s
fmt.Println(s[0])
s2 := s[1].([]interface{})
fmt.Println(s2[0])
s3 := s2[1].([]interface{})
fmt.Println(s3[0])
Output (try it on the Go Playground):
one
one
one
No matter how deep we go, the 2nd element will always be the slice value pointing to the same array as s
, wrapped in an interface{}
value.
The indirection plays the important role as a copy will be wrapped in the interface{}
but the copy will contain the same pointer.
Changing the type to be an array:
s := [2]interface{}{"one", nil}
s[1] = s
fmt.Println(s[0])
s2 := s[1].([2]interface{})
fmt.Println(s2[0])
s3 := s2[1].([2]interface{})
fmt.Println(s3[0])
Output (try it on the Go Playground):
one
one
panic: interface conversion: interface is nil, not [2]interface {}
This is because when the array is wrapped into an interface{}
, a copy will be wrapped - and a copy is not the original array. So s
will have a second value, an interface{}
wrapping an array, but that is a different array whose 2nd value is not set and therefore will be nil
(the zero value of type interface{}
), so attempting to "go into" this array will panic because it is nil
(type assertion fails because not the special "comma, ok" form was used).
Since this s
array does not contain itself, a simple fmt.Println()
will reveal its full content:
fmt.Println(s)
Output:
[one [one <nil>]]
interface{}
wrapping analysisIf you wrap an array in an interface{}
and modify the content of the original array, the value wrapped in the interface{}
is not affected:
arr := [2]int{1, 2}
var f interface{} = arr
arr[0] = 11
fmt.Println("Original array: ", arr)
fmt.Println("Array in interface:", f)
Output:
Original array: [11 2]
Array in interface: [1 2]
If you do the same with a slice, the wrapped slice (since points to the same underlying array) is also affected:
s := []int{1, 2}
f = s
s[0] = 11
fmt.Println("Original slice: ", s)
fmt.Println("Slice in interface:", f)
Output:
Original slice: [11 2]
Slice in interface: [11 2]
Try these on the Go Playground.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With