Funny thing in Go loops

Published on Thu Feb 2, 2017

Last night I ran into some issues with some code written in Go.

After a whole lot of tracking down I isolated the problem to be around loops and ranges!

The symptoms

The code in question reads some JSON from a file into a slice of elements (don’t worry, the real code deals with errors etc.):

type Element struct {
	Id      string
	Content string
}

func read(file string) []Element {
	data, _ := ioutil.ReadFile(file)
	var elements []Element
	json.Unmarshal(&elements, data)
	return elements
}

After reading the Elements I wanted to put them in a map for easy lookup by .Id. So far so good.

The only problem is, that after doing this and getting the Element with a given id from the map - it seemed to be pointing at the wrong Element!

Pointers! … and loop!

Long story short: Pointers where the problem!

Check out the code (sort of) I used to build the map:

func buildMap(elements []Element) map[string]*Element {
	m := make(map[string]*Element)

	for _, e := range elements {
		m[e.Id] = &e
	}

	return m
}

See the problem?!

I was using pointers in the map - which should be all fine - but in this specific case I was making a pointer out of the variable I got from range-ing over a slice.

The problem is that the variable e remains the same in all the loop iterations - and making a pointer of it would just make the same pointer over and over again!

All these pointers will point to the last element in the slice.

The quick and dirty solution

The quick and dirty solution to this problem is to create a copy of the Element in-loop and then make a pointer to that - this would modify the buildMap function only a bit:

func buildMap(elements []Element) map[string]*Element {
	m := make(map[string]*Element)

	for _, e := range elements {
		cpy := e // HACK!
		m[e.Id] = &cpy
	}

	return m
}

Proof?

Check out the faulty example and the correctly functioning one.

I’ve put up a repository with the examples on GitHub as well.