I am constantly thinking about the Go language and how things work. Lately I have been thinking how everything in Go is by value. We see this when we pass values to functions, when we iterate over slices and when we perform type assertions. In every case, copies of the values that are stored in these data structures are returned. When I first started learning Go this threw me off, but I have come to appreciate the reasonability this brings to our code.
I started to question what would happen if I made a copy of an interface value that was storing a value and not a pointer. Would each new interface value get their own copy or would they share the value? To get a definitive answer I wrote a small program to inspect the interface value.
https://play.golang.org/p/KXvtpd9_29
Listing 1
06 package main
07
08 import (
09 "fmt"
10 "unsafe"
11 )
12
13 // notifier provides support for notifying events.
14 type notifier interface {
15 notify()
16 }
17
18 // user represents a user in the system.
19 type user struct{
20 name string
21 }
22
23 // notify implements the notifier interface.
24 func (u user) notify() {
25 fmt.Println("Alert", u.name)
26 }
In Listing 1 on line 14 we see the declaration of an interface type named notifier with a single method named notify. Then on line 19 we have the declaration of a concrete type named user with the implementation of the notifier interface on line 24. We now have an interface and a concrete type to work with.
Listing 2
28 // inspect allows us to look at the value stored
29 // inside the interface value.
30 func inspect(n *notifier, u *user) {
31 word := uintptr(unsafe.Pointer(n)) + uintptr(unsafe.Sizeof(&u))
32 value := (**user)(unsafe.Pointer(word))
33 fmt.Printf("Addr User: %p Word Value: %p Ptr Value: %v\n", u, *value, **value)
34 }
In Listing 2 we have the inspect function that starts on line 30. This function gives us a pointer to the second word of the interface value. With this pointer we can inspect the value of the second word of the interface and the user value that the second word points to. We need to review these values to really understand the mechanics of the interface.
Listing 3
36 func main() {
37
38 // Create a notifier interface and concrete type value.
39 var n1 notifier
40 u := user{"bill"}
41
42 // Store a copy of the user value inside the notifier
43 // interface value.
44 n1 = u
45
46 // We see the interface has its own copy.
47 // Addr User: 0x1040a120 Word Value: 0x10427f70 Ptr Value: {bill}
48 inspect(&n1, &u)
49
50 // Make a copy of the interface value.
51 n2 := n1
52
53 // We see the interface is sharing the same value stored in
54 // the n1 interface value.
55 // Addr User: 0x1040a120 Word Value: 0x10427f70 Ptr Value: {bill}
56 inspect(&n2, &u)
57
58 // Store a copy of the user address value inside the
59 // notifier interface value.
60 n1 = &u
61
62 // We see the interface is sharing the u variables value
63 // directly. There is no copy.
64 // Addr User: 0x1040a120 Word Value: 0x1040a120 Ptr Value: {bill}
65 inspect(&n1, &u)
66 }
Listing 3 shows the main function which starts on line 36. The very first thing we do on line 39 is declare a variable named n1 of interface type notifier set to its zero value. Then on line 40 we declare a variable named u of type user set with the string “bill”.
We want to inspect what the second word of the interface value contains after we assign the concrete user value to the interface, which is done on line 44.
Listing 4
42 // Store a copy of the user value inside the notifier
43 // interface value.
44 n1 = u
45
46 // We see the interface has its own copy.
47 // Addr User: 0x1040a120 Word Value: 0x10427f70 Ptr Value: {bill}
48 inspect(&n1, &u)
Figure 1: What the interface value looks like after the assignment of the user value.
Figure 1 shows a visual of what the interface value looks like after the assignment. We see that the interface value has its own copy of the user value that was assigned. The address of the user value stored inside the interface is not the same as the address of the original user value.
I wrote this code to learn what would happen if I made a copy of an interface value that was assigned a value and not a pointer. Does the new interface value have its own copy as well or is the value shared?
Listing 5
50 // Make a copy of the interface value.
51 n2 := n1
52
53 // We see the interface is sharing the same value stored in
54 // the n1 interface value.
55 // Addr User: 0x1040a120 Word Value: 0x10427f70 Ptr Value: {bill}
56 inspect(&n2, &u)
Figure 2: What both interface values look like after copying the interface value.
Figure 2 shows us the answer. When we make a copy of the interface value, that is all we are making a copy of. The user value that was stored inside of n1 is now being shared with n2. Each interface value is not getting their own copy. They are both sharing the same user value.
How does all this change if we assign the address of the user value instead of the value itself.
Listing 6
58 // Store a copy of the user address value inside the
59 // notifier interface value.
60 n1 = &u
61
62 // We see the interface is sharing the u variables value
63 // directly. There is no copy.
64 // Addr User: 0x1040a120 Word Value: 0x1040a120 Ptr Value: {bill}
65 inspect(&n1, &u)
Figure 3: What the interface value looks like after the assignment of the address.
In figure 3 we see that the interface is now pointing to the user value referenced by the u variable. The address of the u variable is what has been stored inside the second word of the interface value. This means any changes to the user value associated with the u variable will be seen by the interface value.
Conclusion
I allowed myself to get confused about what Go would do when making a copy of an interface value that was storing a value and not a pointer. For a minute I wondered if each copy of the interface value also created a copy of the value referenced by the interface. Since we were “storing” a value and not a pointer. But what we learned is that since an address is always stored, it is the address that is being copied and never the value itself.