Search code examples
goargumentsevaluationorder-of-execution

What is the evaluation order of function arguments?


I'm using this version of Go:

$ go version
go version go1.18 windows/amd64

The results are different when struct A has only one field and B has two or above fields, and it just happens when parameter type is interface.

I'm not sure if that's a bug:

package main

import (
    "fmt"
)

func main() {
    a := A{}
    m("A", a, SetAI(&a))
    b := B{}
    m("B", b, SetBI(&b))
}

type A struct {
    I int
    S string
}

type B struct {
    I int
}

func SetAI(a *A) A {
    a.I = 10
    return *a
}

func SetBI(b *B) B {
    b.I = 10
    return *b
}

func m(name string, arg1, arg2 interface{}) {
    fmt.Println(name+":", arg1, arg2)
}

I expected this output:

A: {10} {10}
B: {10} {10}

Instead I got this:

A: {0 } {10 }
B: {10} {10}

Solution

  • The source of confusion and different output is the order of evaluation of the arguments.

    Look at your example:

    m("A", a, SetAI(&a))
    

    This is a function call, the function value and arguments are evaluated in the usual order:

    Otherwise, when evaluating the operands of an expression, assignment, or return statement, all function calls, method calls, and communication operations are evaluated in lexical left-to-right order. For example, in the (function-local) assignment

    y[f()], ok = g(h(), i()+x[j()], <-c), k()
    

    the function calls and communication happen in the order f(), h(), i(), j(), <-c, g(), and k(). However, the order of those events compared to the evaluation and indexing of x and the evaluation of y is not specified.

    So basically the spec only guarantees that function calls and communication ops happen from left-to-right.

    Your call has arguments "A", a and SetAI(&a). There is no guarantee if the second argument a is evaluated before the &a param passed to SetAI(), and this very much matters because SetAI() modifies a. Since the order is not guaranteed, you can't rely on which will be evaluated first, both order is valid by the spec.

    If you make the evaluation explicit by doing a copy of the struct before, you get the same result:

    a := A{}
    aCopy := a
    m("A", aCopy, SetAI(&a))
    b := B{}
    bCopy := b
    m("B", bCopy, SetBI(&b))
    

    This will output (try it on the Go Playground):

    A: {0 } {10 }
    B: {0} {10}
    

    Or if you want the function call to be evaluated first:

    a := A{}
    ap := SetAI(&a)
    m("A", a, ap)
    b := B{}
    bp := SetBI(&b)
    m("B", b, bp)
    

    This will output 10 for each cases (try this one on the Go Playground):

    A: {10 } {10 }
    B: {10} {10}