Articles

How to compare Go slices?

Problem: You have two slices that potentially share the same memory buffer meaning that appending to one can cause changes in another one. So, how to understand if two slices share memory? Let’s imagine we have a functions like: func IsMemoryShared(a, b []int) bool { ... } And we want to preserve following behaviour: a := []{1, 2, 3} b := []{1, 2, 3} IsMemoryShared(a, a) // true IsMemoryShared(a[:2]) // true IsMemoryShared(a, b) // false IsMemoryShared(a[1:0], b[1:0]) // false It is known that slice in Go backed by a struct and contain a pointer to a place in memory where slice data is stored and two integers - length and capacity of the slice.

How to open window in MacOS programmatically using Swift

Once I face a problem - “How can I create a normal window on Mac OS?”. Definitely it is possible to use XCode, generate new project using very modern tools and start writing events handling functions/callbacks/delegates/etc. But what if I want to do everything using code, for example last available version of Swift. And here I faced a problem cause there are no normal step-by-step guides which will explain how to do it.

Initial Commit

So, I want to Welcome everybody who at any circumstances get to this page. Recently I decided that it will be great at least to save some random things that I found interesting but also try to share it cause I strongly believe that if something become interesting to one person then this person is not alone and there is non zero probability that there is somebody else who can be interested in it.

How to compare Go slices?

February 11, 2022

Problem: You have two slices that potentially share the same memory buffer meaning that appending to one can cause changes in another one. So, how to understand if two slices share memory?

Let’s imagine we have a functions like:

func IsMemoryShared(a, b []int) bool {
    ...
}

And we want to preserve following behaviour:

a := []{1, 2, 3}
b := []{1, 2, 3}

IsMemoryShared(a, a)  // true
IsMemoryShared(a[:2]) // true
IsMemoryShared(a, b)  // false
IsMemoryShared(a[1:0], b[1:0])  // false

It is known that slice in Go backed by a struct and contain a pointer to a place in memory where slice data is stored and two integers - length and capacity of the slice.

Technically you can access the array pointer by dereferencing slice value (may not work in future releases if slice struct will be changed, for example, order of properties will be changed or compiler will decide to reorder struct fields). Playground

a := []int{1, 2, 3}
fmt.Println((unsafe.Pointer)(&a))  // 0xc00000c030

But just accessing array address doesn’t solve the problem cause for “sliced” slice you will get different address, check here:

a := []int{1, 2, 3}
b := a[1:]
fmt.Println((unsafe.Pointer)(&a))  // 0xc00010a000
fmt.Println((unsafe.Pointer)(&b))  // 0xc00010a018

There is a nice trick that becomes possible cause slices in Go are stored as pure arrays in memory which can’t be “resized” but actually got copied when slice needs to grow. So we can technically check the address of the last element in the slice and compare it, let’s try it out:

a := []int{1, 2, 3}
b := a[1:]
fmt.Println((unsafe.Pointer)(&a[len(a)-1])) // 0xc0000be010
fmt.Println((unsafe.Pointer)(&b[len(b)-1])) // 0xc0000be010

But that is not all, in case if slice is cutted from the end it will not work:

a := []int{1, 2, 3}
b := a[:1]
fmt.Println((unsafe.Pointer)(&a[len(a)-1])) // 0xc000016028
fmt.Println((unsafe.Pointer)(&b[len(b)-1])) // 0xc000016018

The solution is to take the address of the last element in the backed array and we know that this array size is defined by a capacity of a slice, see here:

a := []int{1, 2, 3}
b := a[1:2]

addr := func(v []int) unsafe.Pointer {
  v2 := v[:cap(v)]
  return (unsafe.Pointer)(&v2[len(v2)-1])
}

fmt.Println(a, addr(a)) // [1 2 3] 0xc000126008
fmt.Println(b, addr(b)) // [2] 0xc000126008

So basically algorithm is the following:

  1. resize your slice to make len(a) == cap(a) (that changes only len parameter and does not result in allocating of new memory)
  2. get the latest element of the resulting slice
  3. compare the address of the found element

Even if solution looks solid, unfortunatelly, it is not. In Go there is way to change slice capacity down without reallocation:

a := []int{1, 2, 3}
b := a[1:2:2] // <- slice operator can have 3 values

addr := func(v []int) unsafe.Pointer {
      v2 := v[:cap(v)]

  return (unsafe.Pointer)(&v2[len(v2)-1])
}

fmt.Println(a, addr(a)) // [1 2 3] 0xc0000b8010
fmt.Println(b, addr(b)) // [2] 0xc0000b8008

Slice operator actually has two forms, one is common - [start:len] and second one is quite rare - [start:len:cap]. You can’t set a capacity greater than the actual size of the backing memory array but you can change it down. And for such cases there is no easy way to compare slices, one thing that you can do is to check if slices overlap by checking if two arrays overlap but it will also not guarantee the correct result cause two slices can share the same memory but not overlap.