What is going on under the hood with these structs? Will struct be copied?

I'm not uderstanding what is really going on in this piece of code.

The receiver func works on original struct User (because of pointer), so inside of func we changing the original obj. But will the struct address also be original or it will be copy of original 'a'?

func main() {
    a := Address{"Freedom", "Kyiv"}
    u := User{"Valeriy", "Zalyzhnyi", a}
    fmt.Println(a)
    fmt.Println(u)
    u.updateStreet("Peremohy")

    fmt.Println(a)
    fmt.Println(u)
}

func (u *User) updateStreet(street string) {
    u.address.street = street
}

type User struct {
    firstName string
    lastName  string
    address   Address
}

type Address struct {
    street string
    city   string
}

This is my output

{Freedom Kyiv}
{Valeriy Zalyzhnyi {Freedom Kyiv}}
{Freedom Kyiv}
{Valeriy Zalyzhnyi {Peremohy Kyiv}}

From this I understand that u.address is changed, also I see that this 'a' inside of 'u' is different obj from the original one. So what exactly is going on under the hood and in memory? Based on the output this behavior for me is completely unexpected. I was expecting that because of the pointer we work with the original object in both cases ('a' and 'u'). And the second (after func 'update..') printing of fmt.Println(a) will give us {Peremohy Kyiv} because the second fmt.Println(u) gave us {Valeriy Zalyzhnyi {Peremohy Kyiv}}

Upvotes: 0

Views: 308

Answers (1)

Elias Van Ootegem
Elias Van Ootegem

Reputation: 76395

To understand what happens under the hood, it can be useful to visualise what your code does:

a = address{}
u := user{address: a}

breaks down to this:

| variable value        | memory address |
| a = address{}         | 0x000001       |
| u = user{}            | 0x000002       |
| u.address = copy of a | 0x000003       |

So you have allocated memory for 1 instance of user{}, and 2 instances of address{}. The value of the second address instance is an exact copy of the first, at the point when the copy was created.

Now when you call updateStreet, it is invoked on u through a pointer, it doesn't create a copy of the user instance, but rather operates on the memory address 0x000002, so effectively it operates on the same a variable. Therefore the expression:

u.address.street = "foo"

Translates to something like: on the value held in memory address 0x000002, access the field called address, in that field, access the field street and assign it the new value. Let's map this on to the table we have created above:

  0x000002 -> address (which is stored in 0x000003)
              |
              --> set street to "foo"

Once the function returns, we still have the same objects as before, in the same location in memory, but because we've accessed the value of a through its address in memory, the changes made by the updateStreet function have been made to the value of u (because we used the same memory address).

The variable a was copied in the assignment to u.address, so its memory address wasn't known to, or passed on to the updateStreet function, and therefore remains unchanged.

Upvotes: 3

Related Questions