Adam Barnes
Adam Barnes

Reputation: 3193

Why are my changes to a nested struct in an array being lost outside of the function making the changes?

I'm still in the "wrestling with the language" stage of my Go progress, so forgive me for almost definitely missing something very obvious here.

I'm defining two structs, one containing the other. I make an array of the outer structs, pass it to a function, which calls a method on each of the inner structs, modifying their contents. This change is visible within the function, but when adding the outer structs to an array for returning, the outer function can't see the changes. I've tried throwing pointers in everywhere to little avail - somewhat thankfully, because it looked awful.

package main

import "github.com/davecgh/go-spew/spew"

type inner struct {
  ints []int
}

func (i *inner) grow() {
  i.ints = append(i.ints, 0)
}

type outer struct {
  inner inner
}

func growOs(os []outer) (out []outer) {
  for _, o := range os {
    o.inner.grow()
    out = append(out, o)
  }
  spew.Dump("----------during")
  spew.Dump(out)
  return
}

func main() {
  os := []outer{
    outer{},
    outer{},
  }
  spew.Dump("----------pre")
  spew.Dump(os)
  growOs(os)
  spew.Dump("----------post")
  spew.Dump(os)
}

Upvotes: 1

Views: 937

Answers (3)

I159
I159

Reputation: 31109

Pointer argument vs value argument

If a function expects pointer argument it will obtain a pointer to an object thereby each change executed upon the object will have effect on the object itself. If a function expects a value argument it will obtain a copy of an object so each operation will affect an obtained copy not the object.

func recPointer(s *string) {
    fmt.Printf("%p\n", s) // The same object
}

func recValue(s string) {
    fmt.Printf("%p\n", &s) // A new object
}

func main() {
    s := "asdf"
    fmt.Printf("%p\n", &s)
    recPointer(&s)
    recValue(s)
}

Named return values

Go's return values may be named. If so, they are treated as variables defined at the top of the function.

Thereby it becomes clear that named return values are defined in function not in a name space above it. So when you return a named value you do it with a new object (again).

func recValue(s []string) (out []string) {
    fmt.Printf("%p\n", &s) // A new object
    out = append(out, s...)
    // Again new object. It is not the `s` from `main`
    // and obviously not the `s` var from incoming arguments.
    fmt.Printf("%p\n", &out)
    return
}

func main() {
    s := []string{"asdf"}
    fmt.Printf("%p\n", &s)
    recValue(s)
}

Fixed example

Be careful! I replaced spew.Dump with fmt.Printf for demonstrative debug.

// No named return values
// Pointer argument receiver
func growOs(os *[]outer) *[]outer {
    for _, o := range *os {
        o.inner.grow()
        *os = append(*os, o)
    }
    fmt.Printf("%p: %+v \n", os, os)
    return os
}

func main() {
    // `os` is a pointer to the object
    os := &[]outer{
        outer{},
        outer{},
    }
    fmt.Printf("%p: %+v \n", os, os)
    growOs(os)
    fmt.Printf("%p: %+v \n", os, os)
}

Upvotes: 1

BlackMamba
BlackMamba

Reputation: 10254

You should change

func main() {
    os := []outer{
        outer{},
        outer{},
    }
    spew.Dump("----------pre")
    spew.Dump(os)
    growOs(os)
    spew.Dump("----------post")
    spew.Dump(os)
}

to

func main() {
    os := []outer{
        outer{},
        outer{},
    }
    spew.Dump("----------pre")
    spew.Dump(os)
    os = growOs(os)
    spew.Dump("----------post")
    spew.Dump(os)
}

NOTICE: os = growOs(os)

in function

func growOs(os []outer) (out []outer) {
  for _, o := range os {
    o.inner.grow()
    out = append(out, o)
  }
  spew.Dump("----------during")
  spew.Dump(out)
  return
}

you just return a new slice out, you don't modify the input slice os,

So in your main function, the os isn't changed.

Upvotes: 2

OneOfOne
OneOfOne

Reputation: 99195

The o in your loop is a copy, so you either have to:

  1. Change your slice to be []*outer.
  2. Simply use a pointer to each item and modify the items in place.

Ex:

for i := range os {
    o := &os[i]
    o.inner.grow()
    // or just os[i].inner.grow()
}
return os
  1. just assign use the returned slice: os = growOs(os)

Upvotes: 3

Related Questions