Before you go deeper, be aware that according to the specification, the program is correct regardless of whether it gives equal or different addresses for values ββwith zero size, because the specification only states that they can be the same, but they do not require that they be the same.
Spec: size and alignment guarantees:
The type of a structure or array is of zero size if it does not contain fields (or elements, respectively) whose size is greater than zero. Two different zero-sized variables can have the same memory address.
So what you are experiencing is the implementation detail. The decisions taken contain more detailed information and factors, the following explanation is valid and sufficient only for specific examples:
In the first example, the addresses of the base array for your fragments are used only inside the main() function, they do not go into the heap. What you type is the result of comparing addresses. These are just bool values; they do not include address values. Therefore, the compiler prefers to use the same address for the support array a and b .
In your second example, the addresses of auxiliary arrays (more specifically, the addresses of some elements of backup arrays) are used outside the main() function, they are passed and used inside the fmt.Println() function, because you also print these addresses.
We can "prove" this by passing the -gcflags '-m' parameters to the Go tool, asking it to print the result of the escape analysis.
In the first example, saving your code in play.go by running go run -gcflags '-m' play.go , the output is:
./play.go:10:14: "&a == &b" escapes to heap ./play.go:10:29: &a == &b escapes to heap ./play.go:11:14: "&a[0] == &b[0]" escapes to heap ./play.go:11:38: &a[0] == &b[0] escapes to heap ./play.go:8:11: main make([]struct {}, 10) does not escape ./play.go:9:11: main make([]struct {}, 20) does not escape ./play.go:10:26: main &a does not escape ./play.go:10:32: main &b does not escape ./play.go:10:13: main ... argument does not escape ./play.go:11:32: main &a[0] does not escape ./play.go:11:41: main &b[0] does not escape ./play.go:11:13: main ... argument does not escape &a == &b false &a[0] == &b[0] true
As we see, the addresses do not disappear.
Running go run -gcflags '-m' play.go with a second example, output:
./play.go:10:15: a[0] escapes to heap ./play.go:10:20: &a[0] escapes to heap ./play.go:10:20: &a[0] escapes to heap ./play.go:8:11: make([]struct {}, 10) escapes to heap ./play.go:11:14: "&a == &b" escapes to heap ./play.go:11:29: &a == &b escapes to heap ./play.go:12:14: "&a[0] == &b[0]" escapes to heap ./play.go:12:38: &a[0] == &b[0] escapes to heap ./play.go:9:11: main make([]struct {}, 20) does not escape ./play.go:10:13: main ... argument does not escape ./play.go:11:26: main &a does not escape ./play.go:11:32: main &b does not escape ./play.go:11:13: main ... argument does not escape ./play.go:12:32: main &a[0] does not escape ./play.go:12:41: main &b[0] does not escape ./play.go:12:13: main ... argument does not escape {} &{} &a == &b false &a[0] == &b[0] false
As you can see, a[0] , &a[0] go to the heap, so the support array a dynamically allocated and, therefore, it will have a different address than address b .
Let us prove this further. Modify the second example to have a third variable c , the address of which will also not be printed, and compare b with c :
a := make([]struct{}, 10) b := make([]struct{}, 20) c := make([]struct{}, 30) fmt.Println(a[0], &a[0]) fmt.Println("&a == &b", &a == &b) fmt.Println("&a[0] == &b[0]", &a[0] == &b[0]) fmt.Println("&b == &c", &b == &c) fmt.Println("&b[0] == &c[0]", &b[0] == &c[0])
Running go run -gcflags '-m' play.go on this output:
./play.go:11:15: a[0] escapes to heap ./play.go:11:20: &a[0] escapes to heap ./play.go:11:20: &a[0] escapes to heap ./play.go:8:11: make([]struct {}, 10) escapes to heap ./play.go:12:14: "&a == &b" escapes to heap ./play.go:12:29: &a == &b escapes to heap ./play.go:13:14: "&a[0] == &b[0]" escapes to heap ./play.go:13:38: &a[0] == &b[0] escapes to heap ./play.go:14:14: "&b == &c" escapes to heap ./play.go:14:29: &b == &c escapes to heap ./play.go:15:14: "&b[0] == &c[0]" escapes to heap ./play.go:15:38: &b[0] == &c[0] escapes to heap ./play.go:9:11: main make([]struct {}, 20) does not escape ./play.go:10:11: main make([]struct {}, 30) does not escape ./play.go:11:13: main ... argument does not escape ./play.go:12:26: main &a does not escape ./play.go:12:32: main &b does not escape ./play.go:12:13: main ... argument does not escape ./play.go:13:32: main &a[0] does not escape ./play.go:13:41: main &b[0] does not escape ./play.go:13:13: main ... argument does not escape ./play.go:14:26: main &b does not escape ./play.go:14:32: main &c does not escape ./play.go:14:13: main ... argument does not escape ./play.go:15:32: main &b[0] does not escape ./play.go:15:41: main &c[0] does not escape ./play.go:15:13: main ... argument does not escape {} &{} &a == &b false &a[0] == &b[0] false &b == &c false &b[0] == &c[0] true
Since only &a[0] printed, but not &b[0] and &c[0] , so &a[0] == &b[0] will be false , but &b[0] == &c[0] will be true .