-1

I would like to loop through a slice of structs, and populate a struct field (which is a map) by passing in each struct to a function.

I have the below struct

type thing struct {
    topicThing map[string]int
}

and I have the below functions

func main() {
    ths := make([]thing, 0)
    for i := 0; i < 10; i++ {
        var th thing
        ths = append(ths, th)
    }

    for _, th := range ths {
        dothing(&th)
    }

    for _, th := range ths {
        fmt.Println(th.topicThing)
    }
}

func dothing(th *thing) {
    tc := make(map[string]int)
    tc["Hello"] = 1
    tc["Bye"] = 2
    th.topicThing = tc
}

The main function creates a slice of things (refered as ths), and passes each thing to the dothing() function by iterating over them. Within dothing(), I create a new map, populate it with data, and assigns it to the passed in thing's attribute. However, by the time we iterate over ths in the main function to print topicThing of each thing, the map is empty.

Since make() creates objects within the heap, I was hoping it would be accessible even outside of the function scope. Can anyone tell me why this is happening?

P.S. if I change the dothing() function like below:

func dothing(th *thing) {
    th.topicThing["Hello"] = 1
    th.topicThing["Bye"] = 2
}

The code works as expected, meaning the map is populated with data when accessed in the main function.

sk311
  • 97
  • 1
  • 8
  • „ Since make() creates objects within the heap,“ This is completely wrong. – Volker Jan 22 '22 at 16:05
  • @Volker would you mind elaborating or pointing me to an article that explains how memory is allocated? – sk311 Jan 22 '22 at 16:54
  • 1
    Memory is allocated wherever the compiler decides. This is different from compiler to compiler, between versions and architectures. Peek at the disasembly. Formaly Go has just memory, nether heap nor stack. – Volker Jan 22 '22 at 17:07
  • Does this answer your question? [Non-declaration statement outside function body in Go](https://stackoverflow.com/questions/20508356/non-declaration-statement-outside-function-body-in-go) – sk shahriar ahmed raka Jan 22 '22 at 17:45

1 Answers1

5

The range copies your object. So when you do this,

    for _, th := range ths {
        dothing(&th)
    }

you are actually dothing on a copy.

For example, with this main:

func main() {
    ths := make([]thing, 0)
    for i := 0; i < 10; i++ {
        var th thing
        ths = append(ths, th)
    }

    for _, th := range ths {
        dothing(&th)
        fmt.Println(th.topicThing)
    }

it will print the right thing, since we are still working on the copy.

In order to not copy, use the array index:

    for idx, _ := range ths {
        dothing(&ths[idx])
    }
Bes Dollma
  • 383
  • 3
  • 10
  • Thanks for the answer! That makes sense. However, that doesn't explain why the second code works. – sk311 Jan 22 '22 at 16:48
  • The second code works because you are passing a pointer to the real object to `dothing` and not a pointer to a copy. As such the `dothing` will change the intended object. – Bes Dollma Jan 22 '22 at 17:01
  • If the `range` returns a copy, am I not passing in the address of the copy? – sk311 Jan 22 '22 at 17:04
  • You are, and as such the modification that you are doing happens on the copy. When the loop finishes, the copy is destroyed. – Bes Dollma Jan 22 '22 at 17:05
  • @BesDollma: OP means the second version of the code in the question, where `dothing` does `th.topicThing["hello"] = 1`; that works even when working with the copy from `range`. – Nick Matteo Jan 22 '22 at 17:12
  • 1
    @sk311: Your second code works because the copy of `th` contains a copy of `topicThing`, but a map is a reference type, so the copy refers to the same underlying memory that the original did. – Nick Matteo Jan 22 '22 at 17:13
  • @NickMatteo Thank you for clarifying my questions and providing the answer!! – sk311 Jan 22 '22 at 17:22