Why is this map empty when I populate it in a Goroutine?

Issue

type driver struct {
    variables map[string]string
}

var Drivers []driver

func main() {

    driver := driver{
        variables: make(map[string]string),
    }
    Drivers = append(Drivers, driver)

    driver.variables = make(map[string]string) // Commenting this line makes it work, too

    done := make(chan bool) 
    go driver.populate(done)

    <-done

    fmt.Print(Drivers[0].variables)
}

func (this *driver) populate(done chan bool) {
    time.Sleep(500 * time.Millisecond)
    this.variables["a"] = "b"
    done <- true
}

I expected:

map[a:b]

Actual result:

map[]

Playground

Solution

The problem is simple: you have a slice of drivers:

var Drivers []driver

Note that Drivers is a slice of some struct type, not a slice of pointers!

When you append something (or you assign a value to one of its elements):

Drivers = append(Drivers, driver)

That makes a copy of the value appended (or assigned)! So when you do this later:

driver.variables = make(map[string]string)

It will set a new map value to driver.variables, but that is distinct from the value stored in Drivers (more precisely at Drivers[0]).

Later you populate driver.variables, but you print Drivers[0].variables. They are 2 different struct values, with 2 different map values. Goroutines do not play a role here (they are properly synchronized so they shouldn’t anyway).

Would you print driver.variables:

fmt.Print(driver.variables)

You would see (try it on the Go Playground):

map[a:b]

If you comment out this line:

driver.variables = make(map[string]string) // Commenting this line makes it work, too

It would work, but only because even though you have 2 struct values, they have the same map value (same map header pointing to the same map data structure).

You can also make it work if you call driver.populate() on the struct value Drivers[0] (and sticking to printing Drivers[0].variables):

go Drivers[0].populate(done)

// ...

fmt.Print(Drivers[0].variables)

Try this one on the Go Playground.

And you can also make it work if Drivers is a slice of pointers:

var Drivers []*driver

// ...

driver := &driver{
    variables: make(map[string]string),
}

Because driver and Driver[0] will be the same pointer (you will have only one struct value and one map value as the initial map is not accessible anymore). Try this on the Go Playground.

Answered By – icza

Answer Checked By – David Goodson (GoLangFix Volunteer)

Leave a Reply

Your email address will not be published.