goscopedefer-keyword

Go language interview question about defer behavior


I've 2 code examples:

func test() int {
    var x int
    defer func() {

        x++
    }()
    x = 1
    return x
}

func main() {
    fmt.Println(test())
}

It returns: 1. However, the following code example behaves differently:

func test() (x int) {
    defer func() {

        x++
    }()
    x = 1
    return
}

func main() {
    fmt.Println(test())
}

It returns 2.


In order to understand what's going on, I disassembled the code.

For the first code snippet (whose output is 1):

  main.go:19            0x48cf40                64488b0c25f8ffffff      MOVQ FS:0xfffffff8, CX
  main.go:19            0x48cf49                483b6110                CMPQ 0x10(CX), SP
  main.go:19            0x48cf4d                7678                    JBE 0x48cfc7
  main.go:19            0x48cf4f                4883ec58                SUBQ $0x58, SP
  main.go:19            0x48cf53                48896c2450              MOVQ BP, 0x50(SP)
  main.go:19            0x48cf58                488d6c2450              LEAQ 0x50(SP), BP
  main.go:20            0x48cf5d                e83effffff              CALL main.test(SB)
  main.go:20            0x48cf62                e8c9bff7ff              CALL runtime.convT64(SB)
  main.go:20            0x48cf67                488b442408              MOVQ 0x8(SP), AX
  main.go:20            0x48cf6c                0f57c0                  XORPS X0, X0
  main.go:20            0x48cf6f                0f11442440              MOVUPS X0, 0x40(SP)
  main.go:20            0x48cf74                488d0d05090100          LEAQ 0x10905(IP), CX
  main.go:20            0x48cf7b                48894c2440              MOVQ CX, 0x40(SP)
  main.go:20            0x48cf80                4889442448              MOVQ AX, 0x48(SP)
  print.go:274          0x48cf85                488b0524020d00          MOVQ os.Stdout(SB), AX
  print.go:274          0x48cf8c                488d0d0dd70400          LEAQ go.itab.*os.File,io.Writer(SB), CX
  print.go:274          0x48cf93                48890c24                MOVQ CX, 0(SP)
  print.go:274          0x48cf97                4889442408              MOVQ AX, 0x8(SP)
  print.go:274          0x48cf9c                488d442440              LEAQ 0x40(SP), AX
  print.go:274          0x48cfa1                4889442410              MOVQ AX, 0x10(SP)
  print.go:274          0x48cfa6                48c744241801000000      MOVQ $0x1, 0x18(SP)
  print.go:274          0x48cfaf                48c744242001000000      MOVQ $0x1, 0x20(SP)
  print.go:274          0x48cfb8                e84397ffff              CALL fmt.Fprintln(SB)
  print.go:274          0x48cfbd                488b6c2450              MOVQ 0x50(SP), BP
  print.go:274          0x48cfc2                4883c458                ADDQ $0x58, SP
  print.go:274          0x48cfc6                c3                      RET
  main.go:19            0x48cfc7                e8d447fcff              CALL runtime.morestack_noctxt(SB)
  main.go:19            0x48cfcc                e96fffffff              JMP main.main(SB)

For the second code snippet (whose output is 2):

  main.go:18            0x48cf30                64488b0c25f8ffffff      MOVQ FS:0xfffffff8, CX
  main.go:18            0x48cf39                483b6110                CMPQ 0x10(CX), SP
  main.go:18            0x48cf3d                7678                    JBE 0x48cfb7
  main.go:18            0x48cf3f                4883ec58                SUBQ $0x58, SP
  main.go:18            0x48cf43                48896c2450              MOVQ BP, 0x50(SP)
  main.go:18            0x48cf48                488d6c2450              LEAQ 0x50(SP), BP
  main.go:19            0x48cf4d                e84effffff              CALL main.test(SB)
  main.go:19            0x48cf52                e8d9bff7ff              CALL runtime.convT64(SB)
  main.go:19            0x48cf57                488b442408              MOVQ 0x8(SP), AX
  main.go:19            0x48cf5c                0f57c0                  XORPS X0, X0
  main.go:19            0x48cf5f                0f11442440              MOVUPS X0, 0x40(SP)
  main.go:19            0x48cf64                488d0d15090100          LEAQ 0x10915(IP), CX
  main.go:19            0x48cf6b                48894c2440              MOVQ CX, 0x40(SP)
  main.go:19            0x48cf70                4889442448              MOVQ AX, 0x48(SP)
  print.go:274          0x48cf75                488b0534020d00          MOVQ os.Stdout(SB), AX
  print.go:274          0x48cf7c                488d0d1dd70400          LEAQ go.itab.*os.File,io.Writer(SB), CX
  print.go:274          0x48cf83                48890c24                MOVQ CX, 0(SP)
  print.go:274          0x48cf87                4889442408              MOVQ AX, 0x8(SP)
  print.go:274          0x48cf8c                488d442440              LEAQ 0x40(SP), AX
  print.go:274          0x48cf91                4889442410              MOVQ AX, 0x10(SP)
  print.go:274          0x48cf96                48c744241801000000      MOVQ $0x1, 0x18(SP)
  print.go:274          0x48cf9f                48c744242001000000      MOVQ $0x1, 0x20(SP)
  print.go:274          0x48cfa8                e85397ffff              CALL fmt.Fprintln(SB)
  print.go:274          0x48cfad                488b6c2450              MOVQ 0x50(SP), BP
  print.go:274          0x48cfb2                4883c458                ADDQ $0x58, SP
  print.go:274          0x48cfb6                c3                      RET
  main.go:18            0x48cfb7                e8e447fcff              CALL runtime.morestack_noctxt(SB)
  main.go:18            0x48cfbc                e96fffffff              JMP main.main(SB)

Solution

  • The Golang spec says this about defer statements:

    [...] if the deferred function is a function literal and the surrounding function has named result parameters that are in scope within the literal, the deferred function may access and modify the result parameters before they are returned.

    (my emphasis)

    In your first snippet, function test has no named return parameter; x is merely a local variable. Therefore, your defer statement cannot modify the result of function test.

    In your second snippet, function test has a named return parameter x, which is in scope within your function literal. Therefore, the defer statement can (and does) modify the result of function test.