Ronny Herzog
Ronny Herzog

Reputation: 43

Modified slice elements not accessible with map. What am I doing wrong?

I have a slice of a more or less complex struct and I want all elements of this slice to be accessible with a map. The map contains pointers to the slice elements. My problem is now that when I change the content of a slice element, it is not reflected in the map pointing to this element. I.e. If I access the changed elements from the slice, I see the changes. If I however access the element from my map, I don't see the change.

I made an abstract code example you'll find below. Here it gets even more strange since I see the change in one element although all should be changed.

package main

import "fmt"

type Test struct {
    one int
    two *string
}

type List []Test
type MapToList map[int]*Test

func MakeTest() (t List, mt MapToList) {

    t = []Test{}
    mt = make(map[int]*Test)

    var one, two, three string
    one = "one"
    two = "two"
    three = "three"

    t = append(t, Test{1, &one})
    mt[1] = &t[len(t)-1]
    t = append(t, Test{2, &two})
    mt[2] = &t[len(t)-1]
    t = append(t, Test{3, &three})
    mt[3] = &t[len(t)-1]

    return
}

func (s *List) Modify() {
    for index := range *s {
        var str string = "xxx"
        (*s)[index].two = &str
    }
}

func main() {

    t, mt := MakeTest()

    fmt.Println("Orginal")
    for index := range t{
        fmt.Println(index, t[index].one, *t[index].two)
    }

    t.Modify()  

    fmt.Println("Modified List")
    for index := range t{
        fmt.Println(index, t[index].one, *t[index].two)
    }

    fmt.Println("Modified Map")
    for key, value := range mt {
        fmt.Println(key, value.one, *value.two)
    }
}

The output is:

Orginal
0 1 one
1 2 two
2 3 three
Modified List
0 1 xxx
1 2 xxx
2 3 xxx
Modified Map
1 1 one
2 2 two
3 3 xxx

Upvotes: 2

Views: 52

Answers (1)

Grzegorz Żur
Grzegorz Żur

Reputation: 49181

I would just consistently use pointers in slices and maps. That simplifies a lot.

As you are using slices of values, it happens that &t[i] after append operation becomes a pointer to an element in the old discarded slice. When you accessing it you are accessing the element of the old slice. Therefore map is referencing an element of a old slice.

Using pointers solves the problem because there is only one copy of each Test structure and multiple pointers to them. It does not matter whether the pointers are in old slice, new slice or map.

package main

import "fmt"

type Test struct {
    one int
    two *string
}

type List []*Test
type MapToList map[int]*Test

func MakeTest() (t List, mt MapToList) {

    t = []*Test{}
    mt = make(map[int]*Test)

    var one, two, three string
    one = "one"
    two = "two"
    three = "three"

    t = append(t, &Test{1, &one})
    mt[1] = t[len(t)-1]
    t = append(t, &Test{2, &two})
    mt[2] = t[len(t)-1]
    t = append(t, &Test{3, &three})
    mt[3] = t[len(t)-1]

    return
}

func (s *List) Modify() {
    for index := range *s {
        var str string = "xxx"
        (*s)[index].two = &str
    }
}

func main() {

    t, mt := MakeTest()

    fmt.Println("Orginal")
    for index := range t{
        fmt.Println(index, t[index].one, *t[index].two)
    }

    t.Modify()  

    fmt.Println("Modified List")
    for index := range t{
        fmt.Println(index, t[index].one, *t[index].two)
    }

    fmt.Println("Modified Map")
    for key, value := range mt {
        fmt.Println(key, value.one, *value.two)
    }
}

https://play.golang.org/p/KvG3Mj4v1u

The output is

Orginal
0 1 one
1 2 two
2 3 three
Modified List
0 1 xxx
1 2 xxx
2 3 xxx
Modified Map
1 1 xxx
2 2 xxx
3 3 xxx

Upvotes: 1

Related Questions