Unexpected behaviour/data from interface&struct

I have been having difficulties understanding why below code block prints the number value as 2 rather than 0 when I remove additional field from Integer struct.

Play

package main

import (
	"fmt"
)

type Number interface {
	Value() int
}

type Integer struct {
	value int
	additional int
}
func (integer Integer) Value() int{
	return integer.value
}

func main() {
	number, err := getNumber()
	if err != nil {
		panic(err)
	}
	fmt.Printf("number val: %d\n", number.Value())
}
func getNumber() (Number, error) {
	var intNumber Integer
	return intNumber, setInteger(&intNumber)
}
func setInteger(num *Integer) error{
	num.value = 2
	return nil
}
1 Like
func getNumber() (Number, error) {
	var intNumber Integer
	return intNumber, setInteger(&intNumber)
}

The order of evaluation of the operands of the return statement - intNumber and setInteger(&intNumber) - is not specified. It is undefined. It may vary.

The Go Programming Language Specification

Order of evaluation

To guarantee the order, write

func getNumber() (Number, error) {
    var intNumber Integer
    err := setInteger(&intNumber)
    return intNumber, err
}

package main

import (
	"fmt"
)

type Number interface {
	Value() int
}

type Integer struct {
	value      int
	additional int
}

func (integer Integer) Value() int {
	return integer.value
}

func main() {
	number, err := getNumber()
	if err != nil {
		panic(err)
	}
	fmt.Printf("number val: %d\n", number.Value())
}
func getNumber() (Number, error) {
	var intNumber Integer
	err := setInteger(&intNumber)
	return intNumber, err
}

func setInteger(num *Integer) error {
	num.value = 2
	return nil
}

https://play.golang.org/p/9K8TRe3uq6Z

number val: 2

I am not trying to print 2, I am asking why does it prints 2 when I remove additional field from the Integer struct.

I edited the question to clarify my question, sorry for the bad explanation.

I don’t have a clue about this behavior, but just changed the code slightly,
https://play.golang.org/p/IVit28szGXh
moving the call to setInteger out of the return statement, and now it works as expected with and without additional. Maybe this change help understand the behavior here.

Thanks for the answer but again I am not asking how to print 0 or 2, I am asking why this one field changes the result? What is happening in the background, is this a compile optimization, is this a bug or something I am doing wrong(clearly I am not)?

Yep, I got it. I also don’t know why, I was only trying to bring more data to the problem and help somehow find an answer. : )

When I remove field additional , not changing order of the evulation but result changes, how do you explain this? I thought compile optimization might do something, I disabled optimization and inlining but the result was same.

Ahh sorry didn’t see the update.
Somehow the behavior depends on the return type as well, If I change the first return value from Number to Integer it returns always same result regardless of the fields.

Well you referenced Number by &, the evaluation then needs to be done via the function first before you return the values, which means, you get the value of Number.Value() which was set before to 2.

To be honest the construct really puzzled me :), was this just a theroretical question or a sample from production code?

In the latter case, i might be tempted to change the structural design of my code.

You have to take a look at the assembly code that gets generated. When you have the additional field in the struct, then Go actually treats the struct as a struct and keeps it in memory. When you get rid of the additional field, then your Integer struct folds down into a single integer. After the stack size check and FUNCDATA and PCDATA directives in the assembly, you see the first “real” instruction is to just move the literal, 2, into the AX register (I’m on x86_64): The setInteger function was inlined.

What’s really interesting to me, and maybe I’m missing something, is that I don’t see setInteger being called or inlined in the version with the additional field. Anyone else see it?

"".getNumber STEXT size=116 args=0x20 locals=0x38
	0x0000 00000 (main.go:27)	TEXT	"".getNumber(SB), ABIInternal, $56-32
	0x0000 00000 (main.go:27)	MOVQ	TLS, CX
	0x0009 00009 (main.go:27)	MOVQ	(CX)(TLS*2), CX
	0x0010 00016 (main.go:27)	CMPQ	SP, 16(CX)
	0x0014 00020 (main.go:27)	JLS	109
	0x0016 00022 (main.go:27)	SUBQ	$56, SP
	0x001a 00026 (main.go:27)	MOVQ	BP, 48(SP)
	0x001f 00031 (main.go:27)	LEAQ	48(SP), BP
	0x0024 00036 (main.go:27)	FUNCDATA	$0, gclocals┬╖7235335ad9f69aa19497c8c3511e8b84(SB)
	0x0024 00036 (main.go:27)	FUNCDATA	$1, gclocals┬╖7d2d5fca80364273fb07d5820a76fef4(SB)
	0x0024 00036 (main.go:27)	FUNCDATA	$3, gclocals┬╖9fb7f0986f647f17cb53dda1484e0f7a(SB)
	0x0024 00036 (main.go:29)	PCDATA	$2, $0
	0x0024 00036 (main.go:29)	PCDATA	$0, $0
	0x0024 00036 (main.go:29)	XORPS	X0, X0
	0x0027 00039 (main.go:29)	MOVUPS	X0, ""..autotmp_5+32(SP)
	0x002c 00044 (main.go:29)	XCHGL	AX, AX
	0x002d 00045 (main.go:29)	PCDATA	$2, $1
	0x002d 00045 (main.go:29)	LEAQ	go.itab."".Integer,"".Number(SB), AX
	0x0034 00052 (main.go:29)	PCDATA	$2, $0
	0x0034 00052 (main.go:29)	MOVQ	AX, (SP)
	0x0038 00056 (main.go:29)	PCDATA	$2, $1
	0x0038 00056 (main.go:29)	LEAQ	""..autotmp_5+32(SP), AX
	0x003d 00061 (main.go:29)	PCDATA	$2, $0
	0x003d 00061 (main.go:29)	MOVQ	AX, 8(SP)
	0x0042 00066 (main.go:29)	CALL	runtime.convT2Inoptr(SB)
	0x0047 00071 (main.go:29)	PCDATA	$2, $1
	0x0047 00071 (main.go:29)	MOVQ	24(SP), AX
	0x004c 00076 (main.go:29)	MOVQ	16(SP), CX
	0x0051 00081 (main.go:29)	PCDATA	$0, $1
	0x0051 00081 (main.go:29)	MOVQ	CX, "".~r0+64(SP)
	0x0056 00086 (main.go:29)	PCDATA	$2, $0
	0x0056 00086 (main.go:29)	MOVQ	AX, "".~r0+72(SP)
	0x005b 00091 (main.go:29)	PCDATA	$0, $2
	0x005b 00091 (main.go:29)	XORPS	X0, X0
	0x005e 00094 (main.go:29)	MOVUPS	X0, "".~r1+80(SP)
	0x0063 00099 (main.go:29)	MOVQ	48(SP), BP
	0x0068 00104 (main.go:29)	ADDQ	$56, SP
	0x006c 00108 (main.go:29)	RET
	0x006d 00109 (main.go:29)	NOP
	0x006d 00109 (main.go:27)	PCDATA	$0, $-1
	0x006d 00109 (main.go:27)	PCDATA	$2, $-1
	0x006d 00109 (main.go:27)	CALL	runtime.morestack_noctxt(SB)
	0x0072 00114 (main.go:27)	JMP	0
	0x0000 65 48 8b 0c 25 28 00 00 00 48 8b 89 00 00 00 00  eH..%(...H......
	0x0010 48 3b 61 10 76 57 48 83 ec 38 48 89 6c 24 30 48  H;a.vWH..8H.l$0H
	0x0020 8d 6c 24 30 0f 57 c0 0f 11 44 24 20 90 48 8d 05  .l$0.W...D$ .H..
	0x0030 00 00 00 00 48 89 04 24 48 8d 44 24 20 48 89 44  ....H..$H.D$ H.D
	0x0040 24 08 e8 00 00 00 00 48 8b 44 24 18 48 8b 4c 24  $......H.D$.H.L$
	0x0050 10 48 89 4c 24 40 48 89 44 24 48 0f 57 c0 0f 11  .H.L$@H.D$H.W...
	0x0060 44 24 50 48 8b 6c 24 30 48 83 c4 38 c3 e8 00 00  D$PH.l$0H..8....
	0x0070 00 00 eb 8c                                      ....
	rel 12+4 t=16 TLS+0
	rel 48+4 t=15 go.itab."".Integer,"".Number+0
	rel 67+4 t=8 runtime.convT2Inoptr+0
	rel 110+4 t=8 runtime.morestack_noctxt+0
1 Like

I was able to add the GOSSAFUNC=getNumber environment variable before running go build and that got me a set of diffs as the optimizer runs through the SSA that gets generated. I see that the assignment in setNumber starts getting optimized away in the “late opt” (“phase,” is it?), then more in the “dead auto elim” “phase” and then the assignment is completely gone in the “generic deadcode” phase. I think I’ll take a look at getting more output from those optimizers.