I mentioned on Twitter earlier today that very often when I get really stuck on something and finally decide to ask for help on a forum, the answer suddenly hits me as I finish writing my post. Most of the time the problem is something really silly that I just kept completely overlooking, and the answer feels painfully obvious once it clicks. Despite the fact that these might be a little embarrassing to post, I figured rather than letting my entire would-be forum post go to total waste once I finally get what’s happening (and so never end up clicking the “Post” button), it might be nice to post the issue on my blog, if nothing else as a reminder of my own facepalm moments.
Here is one such story from today. After-the-fact comments are added in [square braces].
“Completely unrelated []interface{}!!!”
Hi,
I feel like I’m either sleep deprived or taking crazy pills and would appreciate some help explaining what’s happening here. Here are the two methods involved:
func (a *AggregateAge1685) SetElements(elements []interface {}) {
for _, e := range a.Elements {
a.RemoveElement(e)
}
for _, e := range elements {
a.AddElement(e)
}
}
func (a *AggregateAge1685) RemoveElement(e interface{}) {
uid := e.(string)
for idx, id := range a.Elements {
if id == uid {
a.Elements = append(a.Elements[:idx], a.Elements[idx+1:]...)
break
}
}
}
[As an aside, these two methods are generated by my self modifying simulation experiment!]
Here is what is happening: I pass a slice of two unique elements to
SetElements
. Eg:[]interface{"5,0","7,1"}
When the first element is removed via
a.RemoveElement(e)
, the slice passed toSetElements()
changes to[]interface{"7,1","7,1"}
.
I cannot see why the removal of an element in the
a.Elements
field is modifying the unrelatedelements
slice. What obvious thing am I missing here?
Answer
The answer, of course, is that my “unrelated” elements
slice is far from unrelated. I used to copy the contents of a.Elements
into a new slice with a new backing array and modify the new slice, then pass it back to the Aggregate struct to replace the old elements with the new ones. Last week I changed this (wrongly) to retrieve a.Elements
and then modify a.Elements
…and then forgot about it. Passing a slice by value in Go passes the slice header, but the slice value does not include the elements. The slice header includes the slice capacity, length, and a pointer to the backing array. So I’ve really been acting on the same underlying array in my two []interface{}
s.
Here is a useful link with more information about Go slices: https://blog.golang.org/go-slices-usage-and-internals