Before Go 1.17 it was not possible to get a reference to the backing array of a slice without resorting to the reflect and unsafe packages to inspect the raw slice header. Not ideal.
Some resources refer to "backing array" as the "underlying array". These are the same thing.
However, since 1.17 it is possible to convert a slice to a pointer to an array using a type conversion:
a1 := [3]string{"π", "π", "π"}
s := a1[:]
a2 := (*[3]string)(s)
In the example above, a2 will be equal to the memory address of a1. In other words, &a1 == a2 is true.
If we diagram the situation it looks like this:
There are some limits you should be aware of.
Limit 1: Length of slice
The length of the array can not be greater than the length of the slice, even if the backing array has more capacity.
a1 := [3]string{"π", "π", "π"}
s := a1[:2] // get a slice of len 2
a2 := (*[3]string)(s) // will panic!
The above example will panic. We canβt convert a slice of length 2 to a pointer to an array of length 3.
Limit 2: Begins at slice offset
The pointer to the array will always start at the beginning of the slice. If your slice does not start at the beginning of its backing array your array pointer will not do so either.
a1 := [3]string{"π", "π", "π"}
s := a1[1:] // get a slice of len 2 with offset 1
a2 := (*[2]string)(s)
Since slice s has a length of 2 we can only convert to a *[2]string.
We will not be able to get a reference to the a1[0] element. The diagram below illustrates this.
Reference to the full backing array
Put together, these limits mean that you can only get an array that references the βwindowβ of any specific slice.
If you want a reference to the full backing array of a slice you will need to know how it was created:
- Created via
make, a literal or returned fromappend: The slice will always start at the first element, so you can re-slice up to its capacity and get a reference to the entire backing array. - Created via slicing of an array: You will already have the full array reference.
- Created via re-slicing: Find out how the original slice was created, if it was also re-sliced, follow the originals until you find a non re-sliced slice.
Copy slice to array (1.20 and later)
Since Go 1.20 you also have the option to directly convert slices to new arrays, no pointers required. The limits above regarding the βwindowβ still apply.
a1 := [3]string{"π", "π", "π"}
s := a1[:]
a2 := [3]string(s)
In the above example a2 will be a new array containing the same values as a1.
Run the code below to see the differences between the two conversions.
package main
import (
"fmt"
)
func main() {
original := [3]string{"π", "π", "π"}
s := original[:]
ptr := (*[3]string)(s)
cpy := [3]string(s)
fmt.Printf("&original: %p\n", &original)
fmt.Printf("ptr: %p\n", ptr)
fmt.Printf("&cpy: %p\n", &cpy)
fmt.Printf("ptr == &original: %v\n", ptr == &original)
fmt.Printf("&cpy == &original: %v\n", &cpy == &original)
}
Make privacy claims you can back up
Join 800+ subscribers.
One email every few weeks.
I share what I learn building privacy-friendly systems. From confidential computing to Verbond and the occasional Go tip or trick. You can unsubscribe at any time.
Written by Willem Schots
I help teams prove what happens to their users' data, and I'm building Verbond, which shows where data can go before third-party code runs.
This site started as a place to learn Go. Most posts are now about privacy-friendly and verifiable systems, with the occasional Go post in between.
Want to talk about your system? Book a 30-minute call, or follow me on Bluesky and LinkedIn.