Linked Lists As Map Keys – A Weird Go Trick

Sometimes the simplest things can be surprising. In this installment, we’ll explore Go value equality and a subtlety regarding non-pointer interface values. A hat tip to Nick Johnson, who told me of this trick when discussing his smart EVM disassembler “evmdis”.

Let’s say you’re iterating a hierarchy in Go and you want to track whether a certain path of arbitrary length has been visited. The easiest way is to store all visited paths in a map. This is straightforward if your path is a string, but what if the elements of your path are structured objects? Turns out you can still use a map!

Here’s an example. This is the path element:

type elem struct { name string index int }

Go slices can’t be used as map keys. So you can’t do this:

type path [] elem seen := make ( map [ path ] bool ) seen[ path {{ "foo" , 1}, { "bar" , 2}}] = true

But you can do this:

type path interface {} type elem struct { name string index int link path } seen := make ( map [ path ] bool ) path1 := elem { "foo" , 1, elem { "bar" , 2, nil }} path2 := elem { "foo" , 1, elem { "bar" , 99, nil }} seen[path1] = true fmt. Println ( "seen path1? →" , seen[path1]) fmt. Println ( "seen path2? →" , seen[path2])

seen path1? → true seen path2? → false

What’s happening there? Map keys must be comparable and use the same rules as the equality operator. Intuitively, you can think of Go comparisons as being shallow, not deep. Go values are equal when their content is equal. References are not followed. But there’s a catch for interfaces, which are a kind of reference: Interface values are comparable. Two interface values are equal if they have identical dynamic types and equal dynamic values or if both have value nil. When comparing the elem struct, all fields including link are compared. In our case, the dynamic value of link has type elem or nil – recursion! The equality operator traverses the whole list, checking for deep equality.