Emmanuel Katto Dubai : Issue with Passing Structs by Reference in Recursion

Hey everyone, I"m Emmanuel Katto from Dubai, United Arab Emirates (UAE), I’ve encountered an issue with a recursive function, and I’m hoping to get some advice or suggestions on what might be going wrong.

Here’s a quick overview of the setup:

I have a recursive function play(b) where b is a struct that contains two slices (s and w). These slices hold structs with a few fields: string1, string2, and bool.

Inside the play(b) function, there’s a piece of code like this:

bNew := mm(bIn)
bOrigIn := bIn
play(bNew)

play() calls itself recursively, and the recursion eventually returns back up. The issue I’m seeing is that, after returning from some levels of recursion, bIn and bOrigIn are both different from how they were before the recursive call, even though they should have been unchanged.

The Problem:

  • The function reaches a certain recursion depth (say 23 levels) and then continues deeper for another 10 levels.
  • When returning back to level 23, bIn has changed from its original value. What’s strange is that bOrigIn has also changed in exactly the same way as bIn (which suggests they are somehow linked).
  • However, bNew remains unchanged as expected.

Additional Details:

  • bIn contains slices s and w, each with 9 elements.
  • When returning to level 23, the 9th element of slice w has been modified. Specifically, it now contains the value from the 9th element of slice s, but with the boolean flipped, and the 9th element from s is removed.
  • This change is made by a function mm(bIn) at some point during the recursion.

The Core Questions:

  1. Why is bIn and bOrigIn changing when they should be local variables?
  • Since both bIn and bOrigIn are function-level variables (not package-level), why do they appear to be affected by changes that occur in the recursion?
  1. Is this a slicing issue?
  • I suspect that since Go slices are reference types (i.e., they point to the same underlying array), the changes made in the recursion might be affecting both bIn and bOrigIn because they share references to the same memory locations. If that’s the case, is this why both of them are modified when only one should have been?
  1. What should I look for to prevent this?
  • Are there any best practices for handling slices and recursion in Go to avoid unintended side effects like this? Specifically, should I be copying the slices (or the struct) before passing them into functions like mm() to ensure that changes don’t propagate unexpectedly?

What I’ve Tried:

  • I’ve tried both := and = for creating bOrigIn := bIn, but the issue persists either way.
  • I’m aware that slices in Go are reference types, so any modification to one slice in a function can affect the original slice unless I explicitly copy it.

Looking for Advice:

If anyone has suggestions on how to handle this scenario or ways to avoid these kinds of slice reference issues, I’d greatly appreciate your thoughts!

Thanks for reading!
Emmanuel Katto

It is hard to pinpoint your exact problems without seeing the actual code.

But generally speaking you are right. Several Types like slice and map are actually decorated pointers. So when you use assignment a := b you are making a copy of the pointer, still pointing to the same value. If you want to copy the contents of the slice you will have to use

b := make([]whatever, len(a))
copy(b,a)

But beware - this copy only has depth 1. If the slice is for example a slice of pointers, maps, or maybe structs which contain pointers, then these references will not be duplicated. You would have to use a recursive copy if you also want to make copies of these values.

1 Like

Slices are values; but they hold a pointer to an array. Per this answer, here’s what a slice header looks like:

type SliceHeader struct {
    Data uintptr
    Len  int
    Cap  int
}

From the tour of go:

Slices are like references to arrays

A slice does not store any data, it just describes a section of an underlying array.

Changing the elements of a slice modifies the corresponding elements of its underlying array.

Other slices that share the same underlying array will see those changes.

And here’s a good blog post by Dave Cheney:

https://dave.cheney.net/2018/07/12/slices-from-the-ground-up

2 Likes