Let's say I have the following array of ints of length 3:
nums := [3]int{1,2,3}
Then I grab the slice of just the first two items
numSlice := nums[:2]
Invoking cap
on numSlice and nums yields 3 in both cases, and len
yields 2 and 3 respectively.
If I then append to that slice (numSlice = append(numSlice, 10)
), the underlying array (nums
) is now [1 2 10]
. cap
remains at 3 for both, as the underlying array of the slice is the same, and len for the slice is now 3.
However, if I append to that slice again (numSlice = append(numSlice, 20)
), the underlying array of the slice must change - we see this is the case when cap
now has doubled for numSlice and len is now 4.
Sorry for the overwrought explanation, just walking myself through it, but can someone explain to me what happens under the hood to the underlying array and how to get the reference to the new array?
Like arrays, slices are also used to store multiple values of the same type in a single variable. However, unlike arrays, the length of a slice can grow and shrink as you see fit. In Go, there are several ways to create a slice: Using the []datatype{values} format.
In the Go slice, you can search an element of string type in the given slice of strings with the help of SearchStrings() function. This function searches for the given element in a sorted slice of strings and returns the index of that element if present in the given slice.
Slices in Go and Golang The basic difference between a slice and an array is that a slice is a reference to a contiguous segment of an array. Unlike an array, which is a value-type, slice is a reference type. A slice can be a complete array or a part of an array, indicated by the start and end index.
First, if you haven't already, you should read this official blog post about slice internals. That should clear up everything.
Now to access the underlying array, you can use a combination of reflect
and unsafe
. In particular, reflect.SliceHeader
contains a Data
field which contains a pointer to the underlying array of a slice.
Example adapted from the documentation of the unsafe
package:
s := []int{1, 2, 3, 4}
hdr := (*reflect.SliceHeader)(unsafe.Pointer(&s))
data := *(*[4]int)(unsafe.Pointer(hdr.Data))
Just as a headsup, answering your second question. Starting from Go 1.17, you can do it like this
(*[2]int)(numSlice)
Playground
package main
import (
"fmt"
)
func main() {
nums := [3]int{1, 2, 3}
numSlice := nums[:2]
underArr1 := (*[2]int)(numSlice)
fmt.Println(&underArr1[0]) //0xc000016018
numSlice = append(numSlice, 10)
underArr2 := (*[3]int)(numSlice)
fmt.Println(&underArr2[0]) //0xc000016018 - same
fmt.Println(nums) // [1 2 10]
numSlice = append(numSlice, 20)
underArr3 := (*[3]int)(numSlice)
fmt.Println(&underArr3[0]) //0xc000078030 - different
fmt.Println(cap(numSlice)) // 6
}
To be honest, you don't have to convert to array pointer to see the adresses, I just do it to answer your second question.
The behaviour is indeed the way you described it. When you append 10
, you still have one field left in your underlying array (because its length is 3, but your numSlice is 2), and even though it is currently occupied by a 3
, it can be used, and 3
is overwritten by 10
.
When you append a 20
, there are no fields left, so it creates a new underlying array (most likely 6 fields long, twice as big) and copies all the data from the original array there and moves the pointer to that array.
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