Get the backing array of a slice

Avatar of the author Willem Schots
31 Jul, 2023
~3 min.
RSS

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:

Initially we have slice s backed by a1.
Converting s to a *[3]string gives us a new reference to a1.

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.

Initially we have slice s into a part of a1.
Converting s to a *[3]string will panic.

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.

Initially we have slice s into a part of a1.
Convert s to a *[2]string.
Results in a situation where we can't access a1[0] via the new reference.

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 from append: 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.

Initially we have slice s backed by a1.
Converting s to a [3]string gives us a copy of a1.

Run the code below to see the differences between the two conversions.

main.go
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)
}
🎓

Subscribe to my Newsletter and Keep Learning.

Gain access to more content and get notified of the latest articles:

I send emails every 1-2 weeks and will keep your data safe. You can unsubscribe at any time.

Hello! I'm the Willem behind willem.dev

I created this website to help new Go developers, I hope it brings you some value! :)

You can follow me on Twitter/X, LinkedIn or Mastodon.

Thanks for reading!