Funky evaluation order of function arguments

398 views
Skip to first unread message

Martin Bruse

unread,
Mar 25, 2015, 3:00:23 AM3/25/15
to golan...@googlegroups.com
Hello nuts!

In http://play.golang.org/p/OuvJp26sSv I noticed something strange.

The specs say "At package level, initialization dependencies determine the evaluation order of individual initialization expressions in variable declarations. 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."

But how does Go evaluate function arguments?

In the first part of main(), the first println argument prints <nil> as expected, but the second is printed as if the "set to 20" anon func has already run.

In the second part of main(), both the first and second arguments to println2 prints as if the "set to 20" anon func has run.

Firstly I assume that anon functions somehow affect the evaluation order of function arguments, but it seems to affect it differently depending on whether the function takes interface arguments or not.

Just to assert that it works as expected without anon funcs among the arguments the third part panics when I try to dereference a nil pointer.

Can someone explain this?

Martin Bruse

unread,
Mar 25, 2015, 3:02:39 AM3/25/15
to golan...@googlegroups.com
Right, I sent the wrong link. http://play.golang.org/p/j0vBBmR8CI is the right one.

Dave Cheney

unread,
Mar 25, 2015, 6:30:12 AM3/25/15
to golan...@googlegroups.com
The evaluation order of function arguments is not defined. It is implement ation dependent.

http://dave.cheney.net/2013/11/15/evaluation-order-oddity

Martin Bruse

unread,
Mar 25, 2015, 6:33:22 AM3/25/15
to Dave Cheney, golang-nuts
Thank you, good to know which cases to avoid!

On Wed, Mar 25, 2015 at 11:30 AM, Dave Cheney <da...@cheney.net> wrote:
> The evaluation order of function arguments is not defined. It is implement ation dependent.
>
> http://dave.cheney.net/2013/11/15/evaluation-order-oddity
>
> --
> You received this message because you are subscribed to a topic in the Google Groups "golang-nuts" group.
> To unsubscribe from this topic, visit https://groups.google.com/d/topic/golang-nuts/HQ59BBbhCT0/unsubscribe.
> To unsubscribe from this group and all its topics, send an email to golang-nuts...@googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.

gi...@iki.fi

unread,
Mar 25, 2015, 8:11:35 AM3/25/15
to golan...@googlegroups.com
At least for me this seems very confusing.

The writing doesn't seem to explain what the cause is. Just a reference "because the evaluation order of this sort of expression is not specified".

Also, the spec goes on to give an example of a situation and an other-wordly advice "order of those events compared to the evaluation and indexing of x and the evaluation of y is not specified".

Could someone clarify this for my feeble mind?

Volker Dobler

unread,
Mar 25, 2015, 9:23:33 AM3/25/15
to golan...@googlegroups.com
Am Mittwoch, 25. März 2015 13:11:35 UTC+1 schrieb gi...@iki.fi:
At least for me this seems very confusing.

The writing doesn't seem to explain what the cause is. Just a reference "because the evaluation order of this sort of expression is not specified".

The "cause" is: Any (existing or yet to be written) Go compiler may
arbitrarily choose any evaluation order it likes, even on Mondays
a different one than on Wednesdays. The Go language does not
state which result of these types of computations is "the" correct one.

If you ask someone "Bring me A and B from the garage!"  he may bring
A first and B second or the other way around and there might be no
real "cause": He was free to choose and he did choose. Maybe next time
he'll choose to bring B first.

That is what is happening here: You asked the Go compiler "Evaluate this"
and the Go compiler evaluated one sub-expression before the other as he
is free to do so as the "evaluation order is not specified".

If you cannot live with that ambiguity (most likely) then you'll have to split
your code into several lines like Dave explains.

V.

gi...@iki.fi

unread,
Mar 25, 2015, 12:24:59 PM3/25/15
to golan...@googlegroups.com
Ok. So would the following be correct?

<spec> "when evaluating the operands of an expression"

  <me> such as: x + fn()

<spec> "all function calls, method calls, and communication operations
       are
evaluated in lexical left-to-right order"*


  <me> and because x is none of those, it's evaluation order is
       not constrained in relation to the other operands in the
       expression (is left up to the implementation)


  <me> thus given:
       x := 1
       fn := func() int { x = 2; return 0 }

  <me> the following expression:
       x + fn() + x

  <me> could be evaluated in any of the following three ways:
       x +    // 1
       x +    // 1
       fn()   // 0, but x=2
       // evaluating to 2

    
    
    
       x +    // 1
       fn() + // 0, but x=2
       x      // 2
       // evaluating to 3

    
    
    
       fn() + // 0, but x=2
       x +    // 2
       x      // 2
       // evaluating to 4

  <me> and it's not possible to change the evaluation order
       by using parentheses

Sounds weird to say the least. Did I misunderstand?

-Gima
--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.

gi...@iki.fi

unread,
Mar 25, 2015, 12:39:25 PM3/25/15
to golan...@googlegroups.com
In addition, if I have the said expression x + fn() + x, where fn() touches x by writing to it, shouldn't the memory model enforce a happens-before relationship to be exactly x, then fn(), then x?

adon...@google.com

unread,
Mar 25, 2015, 12:43:01 PM3/25/15
to golan...@googlegroups.com
No, I think you understand perfectly.  You explained it beautifully.

To change the evaluation order, break the complex expression into several statements.

Ian Lance Taylor

unread,
Mar 25, 2015, 7:44:28 PM3/25/15
to gi...@iki.fi, golang-nuts
On Wed, Mar 25, 2015 at 9:39 AM, <gi...@iki.fi> wrote:
>
> In addition, if I have the said expression x + fn() + x, where fn() touches
> x by writing to it, shouldn't the memory model enforce a happens-before
> relationship to be exactly x, then fn(), then x?

The memory model is about happens-before relationships for code
executing in different goroutines. It's not about code that is
running in the same goroutine, as is the case here.

Ian

gi...@iki.fi

unread,
Mar 25, 2015, 8:10:38 PM3/25/15
to Ian Lance Taylor, golang-nuts

To my understanding the memory model must also specify the happens-before relationship within one goroutine. In this case it specifies it to be the program order. Which brings me to my mistake in bringing this up, as it’s not the memory model’s fault that the evaluation order is what is is, but the evaluations happen in some order, which then must satisfy the happens-before requirements of the memory model.

Bottom line: I mistakenly though memory model had something to do with this, whereas the problem arises solely from the unspecified order of evaluation.

Yet I am still confused by the fact that I cannot change the evaluation order by using parentheses. Of the three possible evaluation orders, the go compiler chooses to use “evaluate function first”, then the rest (fn() + x + x = 4), but shouldn’t it be possible to force the evaluation order by saying (x + fn()) + x = 3?
Test: https://play.golang.org/p/fgIJlI5nup

Sorry if I'm totally in the woods with this :(

-Gima

Ian Lance Taylor

unread,
Mar 25, 2015, 8:25:29 PM3/25/15
to gi...@iki.fi, golang-nuts
On Wed, Mar 25, 2015 at 5:10 PM, <gi...@iki.fi> wrote:
>
> Yet I am still confused by the fact that I cannot change the evaluation
> order by using parentheses. Of the three possible evaluation orders, the go
> compiler chooses to use “evaluate function first”, then the rest (fn() + x +
> x = 4), but shouldn’t it be possible to force the evaluation order by saying
> (x + fn()) + x = 3?

The language could have been written such that parentheses affect
evaluation order. Some languages do work that way. However, Go does
not. In Go, parentheses affect assocation of operations. They do not
affect order of evaluation.

In the early days we discussed order of evaluation quite a bit. There
is a tradeoff between giving the compiler freedom to optimize and
giving the programmer a clear understanding of their code. For
example, given x + fn() + x, where x is a global variable, or where fn
may be a function closure that may refer to x, you get better code if
the compiler loads x from memory only once. If the order of
evaluation is completely fixed, then the compiler would have to load
x, then call fn, then load x again. That is slower code for a case
that does not matter to 99.9% of programs. The cost is that for the
0.1% of programs, the result may be confusing. I'm not going to claim
that Go necessarily made the right tradeoff, but there is a tradeoff
to be made, and Go made a choice.

Ian

gi...@iki.fi

unread,
Mar 25, 2015, 8:37:37 PM3/25/15
to Ian Lance Taylor, golang-nuts
My thirst for understanding is sated. Thank you for the insights, great
explanation ^,^

-Gima

On 26.3.2015 2:25, Ian Lance Taylor wrote:

Dave Cheney

unread,
Mar 25, 2015, 9:10:27 PM3/25/15
to golan...@googlegroups.com
To quote the memory model document
"Within a single goroutine, reads and writes must behave as if they executed in the order specified by the program."

gi...@iki.fi

unread,
Mar 25, 2015, 9:26:15 PM3/25/15
to golan...@googlegroups.com

I apologize, I really do. I must be becoming a bother by now. I just Cannot go to sleep without getting this off my chest.

I can understand that the evaluation order doesn’t constrain function and variable evaluation in an expression, but I cannot understand how the following can be allowed and correct behavior:

https://play.golang.org/p/ZDaNFYS4Rm

The outcome is 3 with C, Javascript and Java, whereas go compiler says it’s 4 (which clearly indicates that fn() was called twice before evaluating either of the parens, which with the parentheses present, seems outright wrong.

So is this indeed correct behavior? If so, I’ll just be done with it.

Dave Cheney

unread,
Mar 25, 2015, 9:31:48 PM3/25/15
to golan...@googlegroups.com
Ok, so it's possible to provide an example that might be deducible by a sufficiently smart compiler (to), but in general this is not practical which leads to the current implementation defined behavior.

As Alan said, if you want to assert a specific sequence of operations, you should break the expression into multiple lines.

Alan Donovan

unread,
Mar 25, 2015, 9:34:22 PM3/25/15
to gi...@iki.fi, golang-nuts
It's one possible correct behaviour.  The result depends on how the compiler orders the evaluation of the subexpressions.  The only guarantee is that the first call to fn() is evaluated before the second, but the relative order of the two loads from variable x (and the two loads from variable fn, not that it matters here) is not specified.

I wouldn't be surprised if other compilers give different results, and if results vary by compiler optimization level.


--
You received this message because you are subscribed to a topic in the Google Groups "golang-nuts" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/golang-nuts/HQ59BBbhCT0/unsubscribe.
To unsubscribe from this group and all its topics, send an email to golang-nuts...@googlegroups.com.

Alan Donovan

unread,
Mar 25, 2015, 9:37:59 PM3/25/15
to gi...@iki.fi, golang-nuts
On 25 March 2015 at 21:34, Alan Donovan <adon...@google.com> wrote:
On 25 March 2015 at 21:25, <gi...@iki.fi> wrote:

The outcome is 3 with C, Javascript and Java, whereas go compiler says it’s 4


By the way, the outcome is not 3 with C, it's unspecified too.  It may have been 3 when you tried it, but that doesn't mean it will be next time.

Ian Lance Taylor

unread,
Mar 25, 2015, 11:54:22 PM3/25/15
to Alan Donovan, gi...@iki.fi, golang-nuts
It's worse than unspecified, it's actually undefined. C11 section 6.5
paragraph 2: "If a side effect on a scalar object is unsequenced
relative to either a different side effect on the same scalar object
or a value computation using the value of the same scalar object, the
behavior is undefined. If there are multiple allowable orderings of
the subexpressions of an expression, the behavior is undefined if such
an unsequenced side effect occurs in any of the orderings."

This is bad because it means that the program has no meaning at all,
and the compiler is free to reduce it to a no-op. That may sound
crazy but there are compilers that will do exactly that. They do it
because knowing that a valid program will never trigger undefined
behaviour provides significant optimization opportunities.

Ian

Ian Lance Taylor

unread,
Mar 25, 2015, 11:55:26 PM3/25/15
to gi...@iki.fi, golang-nuts
On Wed, Mar 25, 2015 at 6:25 PM, <gi...@iki.fi> wrote:
>
> The outcome is 3 with C, Javascript and Java, whereas go compiler says it’s
> 4 (which clearly indicates that fn() was called twice before evaluating
> either of the parens, which with the parentheses present, seems outright
> wrong.

Parenthese have absolutely nothing to do with order of evaluation.
Nothing at all. This is true in both Go and C. I don't know offhand
about Javascript or Java.

Ian

gi...@iki.fi

unread,
Mar 26, 2015, 5:59:46 AM3/26/15
to golan...@googlegroups.com
Ah, after a well-rested night and semi-uncertainty if I upset someone,
I'm glad to find that's not the case.
Thank you all for your time and answers.

-Gima
Reply all
Reply to author
Forward
0 new messages