literal maps with duplicate keys

2,524 views
Skip to first unread message

roger peppe

unread,
Jan 24, 2013, 12:28:49 PM1/24/13
to golang-nuts
is the result of this program well-defined?

http://play.golang.org/p/vz7LEIJSFR

Jan Mercl

unread,
Jan 24, 2013, 12:40:19 PM1/24/13
to roger peppe, golang-nuts
On Thu, Jan 24, 2013 at 6:28 PM, roger peppe <rogp...@gmail.com> wrote:
> is the result of this program well-defined?
>
> http://play.golang.org/p/vz7LEIJSFR

IMO yes. From http://golang.org/ref/spec#Composite_literals
----
It is an error to specify multiple elements with the same field name
or constant key value.
----

So I think the compiler should reject this program.

-j

Donovan Hide

unread,
Jan 24, 2013, 12:45:09 PM1/24/13
to Jan Mercl, roger peppe, golang-nuts



-j

--



roger peppe

unread,
Jan 24, 2013, 12:55:09 PM1/24/13
to Jan Mercl, golang-nuts
On 24 January 2013 17:40, Jan Mercl <0xj...@gmail.com> wrote:
i'm not using constant key values.

i'm happy with the fact the code works - i'm just wondering
if i will always be guaranteed that the last element with the
same key will win.

it seems a sensible enough strategy to use (add each element
in turn) but i'm not sure the spec defines it.

minux

unread,
Jan 24, 2013, 12:58:02 PM1/24/13
to Jan Mercl, roger peppe, golang-nuts
should it reject this program?

If we want to reject it, we can only do it at run-time.

i think the compiler is only able to enforce the spec you quote with const
keys. we probably should clarify that in the spec.

minux

unread,
Jan 24, 2013, 1:00:21 PM1/24/13
to roger peppe, Jan Mercl, golang-nuts
On Fri, Jan 25, 2013 at 1:55 AM, roger peppe <rogp...@gmail.com> wrote:
i'm not using constant key values.

i'm happy with the fact the code works - i'm just wondering
if i will always be guaranteed that the last element with the
same key will win.

it seems a sensible enough strategy to use (add each element
in turn) but i'm not sure the spec defines it.
i agree. we should define the order of map insertion for this case
in the spec. please file an issue if you agree.

Jan Mercl

unread,
Jan 24, 2013, 1:53:43 PM1/24/13
to roger peppe, golang-nuts
On Thu, Jan 24, 2013 at 6:55 PM, roger peppe <rogp...@gmail.com> wrote:
> i'm not using constant key values.

Believe me or not but only now I can see `var a = ...`. My previous
answer applies to `const a = ...`, which I read instead. Even though
it clearly never was there.

-j

Donovan Hide

unread,
Jan 24, 2013, 2:54:49 PM1/24/13
to minux, roger peppe, Jan Mercl, golang-nuts

i agree. we should define the order of map insertion for this case
in the spec. please file an issue if you agree.

Would this complicate even further the memory model explanation:


Specifically:

Reads and writes of values larger than a single machine word behave as multiple machine-word-sized operations in an unspecified order.

You'd have to make and exception for the ElementList in a map type composite literal. Obviously a map that is being initialised and read from at the same time is a programmer error, but the memory model definition contradicts the proposal that the insertion order is guaranteed...

minux

unread,
Jan 24, 2013, 3:23:54 PM1/24/13
to Donovan Hide, roger peppe, Jan Mercl, golang-nuts
That is a different problem.

we are talking about the order of map assignments in a particular goroutine,
and that order should be fully specified.

or put it another way, for this code block:
     s = "abc"
     s = "def"
we are talking about assignment of "def" to s happens after assignment of "abc", so
after those statements, s should be "def" (observed in the same goroutines executing
those assignments).

what the memory model docs talks about is this:
because a string contains a pointer and a size, it's bigger than a machine word, so the
precise order or assigning the size and the pointer as observed by another goroutine
is unspecified.

Donovan Hide

unread,
Jan 24, 2013, 3:33:36 PM1/24/13
to minux, roger peppe, Jan Mercl, golang-nuts
Thanks for explaining that, make sense now. Tried to test the map initialisation in a horribly convoluted way, but from this explanation, I'd guess the second print in this example is unsafe because the C map struct assigned to m is bigger than a 64 bit word? The first print is safe because the assignment has not yet occurred?


Horrible code, but just trying to understand the memory model!



Rémy Oudompheng

unread,
Jan 24, 2013, 3:38:22 PM1/24/13
to Donovan Hide, minux, roger peppe, Jan Mercl, golang-nuts
On 2013/1/24 Donovan Hide <donov...@gmail.com> wrote:
> Thanks for explaining that, make sense now. Tried to test the map
> initialisation in a horribly convoluted way, but from this explanation, I'd
> guess the second print in this example is unsafe because the C map struct
> assigned to m is bigger than a 64 bit word?

It's simply unsafe, regardless of the size of maps (which are
pointers, hence a machine word).

>
> http://play.golang.org/p/XEYPm81dax
>

Rémy.

Kevin Gillette

unread,
Jan 25, 2013, 1:01:38 PM1/25/13
to golan...@googlegroups.com, Jan Mercl, roger peppe
Certainly interesting behavior -- I stumbled upon http://play.golang.org/p/xmyvYZmesI before reading your post.

Kevin Gillette

unread,
Jan 25, 2013, 2:01:21 PM1/25/13
to golan...@googlegroups.com, Jan Mercl, roger peppe, itmi...@gmail.com
http://play.golang.org/p/mfmdzGrTLM

On Friday, January 25, 2013 11:34:23 AM UTC-7, itmi...@gmail.com wrote:
On Friday, January 25, 2013 8:01:38 PM UTC+2, Kevin Gillette wrote:
Certainly interesting behavior -- I stumbled upon http://play.golang.org/p/xmyvYZmesI before reading your post.

Jan Mercl

unread,
Jan 25, 2013, 2:30:36 PM1/25/13
to Kevin Gillette, golang-nuts


On Jan 25, 2013 8:01 PM, "Kevin Gillette" <extempor...@gmail.com> wrote:
>
> http://play.golang.org/p/mfmdzGrTLM
>

Can't touch this: http://play.golang.org/p/QIHQoO2okk

-j

Kevin Gillette

unread,
Jan 25, 2013, 3:29:00 PM1/25/13
to golan...@googlegroups.com, Jan Mercl, roger peppe, itmi...@gmail.com
I agree to some extent, but this feels like behavior that has compile-time and run-time semantics. For example, identifiers used as expressions seem to be bound to the value at the identifier's address at the time, while other expressions seem to group by value, and finally some value for every key is chosen, but the precedence is undefined (not that I'm saying it needs to be).

On Friday, January 25, 2013 12:02:38 PM UTC-7, itmi...@gmail.com wrote:
Sounds like closure evaluations...

Jan Mercl

unread,
Jan 26, 2013, 6:59:27 AM1/26/13
to minux, roger peppe, golang-nuts
On Thu, Jan 24, 2013 at 7:00 PM, minux <minu...@gmail.com> wrote:
> i agree. we should define the order of map insertion for this case
> in the spec. please file an issue if you agree.

I cannot find the issue already reported so I'm continuing the discussion here.

Ad "we should define the order of map insertion for this case in the
spec.". I'm not sure if that helps or if it even can be defined. Let's
have this program (also http://play.golang.org/p/bjZ5sodyEq)

package main

import (
"fmt"
)

var a = 0

var m map[int]int

func clr() { m = map[int]int{} }

func main() {
clr()
m[a] = 1
m[a] = 2
fmt.Println("a1) Order 1, 2:", m)

clr()
m[a] = 2
m[a] = 1
fmt.Println("a2) Order 2, 1:", m)

clr()
m = map[int]int{
a: 1,
a: 2,
}
fmt.Println("a3) Order none:", m)

fmt.Println()

clr()
m[0] = 1
m[m[0]] = 2
fmt.Println("b1) Order 1, 2:", m)

clr()
m[m[0]] = 2
m[0] = 1
fmt.Println("b2) Order 2, 1:", m)

clr()
m = map[int]int{
m[0]: 1,
m[m[0]]: 2,
}
fmt.Println("b3) Order none:", m)

clr()
m = map[int]int{
m[m[0]]: 2,
m[0]: 1,
}
fmt.Println("b4) Order ????:", m)
}

The output is

a1) Order 1, 2: map[0:2]
a2) Order 2, 1: map[0:1]
a3) Order none: map[0:2]

b1) Order 1, 2: map[0:1 1:2]
b2) Order 2, 1: map[0:1]
b3) Order none: map[0:2]
b4) Order ????: map[0:1]


Note that while a3 can be produced by choosing one specific order (a1)
of map insertion; b3 is none of b1 or b2. IOW, IMO no specifying or
order of map insertion (using a literal) can reproduce b3. But then
there is b4 which _can_ be reproduce by specifying b2.

AFAICS, the evaluation order, in the case of a literal, is not
"horizontal" but implemented by first evaluation all the key
expressions ("vertically") and then execution the "horizontal"
insertions (in source order). This hypothesis could be probably
[dis]proven by some even more convoluted code with m[expr] used also
on the "value" sides of 'key: value'. But I think it's better to ask
here and hope someone well versed in the compiler implementation can
give the correct answer. And if it's like I think, then I'm afraid it
has to be changed in order to be able to specify the insertion order
(reminder: case b3 cannot be explained by any "horizontal"
[re]ordering).

Alternatively, I'm maybe again missing something obvious..? ;-)

-j

Donovan Hide

unread,
Jan 26, 2013, 7:08:13 AM1/26/13
to Jan Mercl, minux, roger peppe, golang-nuts
 m = map[int]int{
        m[m[0]]: 2,
        m[0]:    1,
 }

just to be clear in this case, m[m[0]] is evaluated as m[0] because m[0] does not exist at the point in time and is returning the nil/default value of int. In other words, m[m[123]] gives the same result.


Dan Kortschak

unread,
Jan 26, 2013, 7:14:06 AM1/26/13
to Jan Mercl, minux, roger peppe, golang-nuts
I think the situation is less confusing than you are making it (I hope it's mot just me being simple). I posted an explanation here [1].

[1]https://plus.google.com/communities/114112804251407510571/stream/d10c4ead-f723-4d60-8c42-d9acf5783383
> --
>
>

Jan Mercl

unread,
Jan 26, 2013, 9:10:00 AM1/26/13
to Dan Kortschak, minux, roger peppe, golang-nuts
On Sat, Jan 26, 2013 at 1:08 PM, Donovan Hide <donov...@gmail.com> wrote:
> m = map[int]int{
> m[m[0]]: 2,
> m[0]: 1,
> }
>
> just to be clear in this case, m[m[0]] is evaluated as m[0] because m[0]
> does not exist at the point in time and is returning the nil/default value
> of int. In other words, m[m[123]] gives the same result.

Yep, I think that's known to all of us. I don't get how it relates to
the insertion order specification (b3 not derived from b1 and b3 not
derived from b2).

On Sat, Jan 26, 2013 at 1:14 PM, Dan Kortschak
<dan.ko...@adelaide.edu.au> wrote:
> I think the situation is less confusing than you are making it (I hope it's mot just me being simple). I posted an explanation here [1].
>
> [1]https://plus.google.com/communities/114112804251407510571/stream/d10c4ead-f723-4d60-8c42-d9acf5783383

AFAIC, you're depicting there the very same [evaluation phases]
hypothesis as I've done in my previous post (and you should be
credited for priority). It's perfectly possible that I have made a
mistake in the derivation. Can you please point out the place of the
error bellow? Thanks in advance.

----

To reiterate it more condensed and b/c as I may have not state it clearly:

The intent of

"we should define the order of map insertion for this case in
the spec." (1)

cannot be reached b/c

none of the possible order specification {{b1, b2}, {b2, b1}}
can produce the current implementation's b3, (2)

which, if correct, requires one of

a change of the implementation (3)

or

a change of the specification to reflect the implementation (4)

but (4) cannot be specified by (1) as long is that means [the intuitive] cca

the [literal's] key, value pairs are evaluated [left to right|other way]
and inserted into the map in the [ascending|reverse|unspecified]
source order (5)

or any otherwise formulated specifications still semantically equal to (5).

-j

minux

unread,
Jan 26, 2013, 9:16:42 AM1/26/13
to Jan Mercl, roger peppe, golang-nuts
my attempt at specifying the composite map literal initialization rule:
1. hoist out all expressions used as key or values that might have side effects
(functions calls, method calls, receive operations), and give each one an temporary
name, use that temporary name to replace the corresponding expression in
the composite literal; execute these expressions in the order they appear in
the source code and assign their result to the corresponding temporary name.

2. an temporary map is created with the various map assignment executing
in the order specified in the source code.

3. finally the constructed map in step 2 is assigned to the left hand side.

4. the compiler is only required to report error if two equal constants are used
as keys in the same composite map literal.

Donovan Hide

unread,
Jan 26, 2013, 9:40:49 AM1/26/13
to Jan Mercl, Dan Kortschak, minux, roger peppe, golang-nuts
Yep, I think that's known to all of us. I don't get how it relates to
the insertion order specification (b3 not derived from b1 and b3 not
derived from b2).

I was just commenting that using zero values of map lookups in a map initialisation example just makes it doubly complicated to parse. Minux's example seems to cover all bases succinctly and understandably.

Jan Mercl

unread,
Jan 26, 2013, 10:40:41 AM1/26/13
to minux, roger peppe, golang-nuts
On Sat, Jan 26, 2013 at 3:16 PM, minux <minu...@gmail.com> wrote:
> my attempt at specifying the composite map literal initialization rule:
> 1. hoist out all expressions used as key or values that might have side
> effects
> (functions calls, method calls, receive operations), and give each one an
> temporary
> name, use that temporary name to replace the corresponding expression in
> the composite literal; execute these expressions in the order they appear in
> the source code and assign their result to the corresponding temporary name.
>
> 2. an temporary map is created with the various map assignment executing
> in the order specified in the source code.
>
> 3. finally the constructed map in step 2 is assigned to the left hand side.
>
> 4. the compiler is only required to report error if two equal constants are
> used
> as keys in the same composite map literal.
>
> http://play.golang.org/p/inaHlQRYgQ

I think such specification will work. However, I find it a bit too
complex. Let me attempt a simpler one, while changing the behavior wrt
your proposal and making it _not compatible in corner cases with the
current implementation_:

Insert into the appropriate place of
http://tip.golang.org/ref/spec#Composite_literals something in the
meaning of:
----
All expressions in a composite literal are assigned to their
respective destinations/LHSs in source order[4]. A LHS expression
constituting a slice/array index or a map key is [fully] evaluated
before its respective RHS expression[1][3]. While evaluating any of
the composite literal [sub]expressions, any existing side effects,
possibly affecting the evaluated-being literal value, are not
guaranteed to be effective[2].
----
[1]: Which is again effectively source order.
[2]: Intermediate (partial) results may be constructed in the process
of building the final value (as in [4]) of the composite literal. Self
referential cycles/tricks may not work while the literal value is not
finished as they may, for example, refer to values of [sub]expressions
which have been already copied to the final result, etc.
[3]: The existing double const map key rule stays as it is.
[4]: With the common nesting effects in place, so in `m := t{f: u{v,
w}, g: h}` the assigned "places" effective order is v->tmp_u.field1,
w->tmp_u.field2, tmp_u->tmp_f.f, h->tmp_f.g, tmp_f->m.

-j

David DENG

unread,
Jan 26, 2013, 11:27:23 AM1/26/13
to golan...@googlegroups.com, roger peppe, Jan Mercl
I prefer the specs define the order of assignment in this situation to be random. So that careful person will never write this kinda fragment, since they are not easily understood and  maintained.

David

Kevin Gillette

unread,
Jan 26, 2013, 1:52:42 PM1/26/13
to golan...@googlegroups.com, roger peppe, Jan Mercl
I agree. or "In a map literal, the rules are undefined for resolving the result of using multiple expressions as keys which evaluate to be equal, or using identifiers which change during evaluation of the literal as keys or values. You should not depend on the observed behavior."

I personally have never used map literals for anything but constant data (though I'm sure many people use expressions). It's still a situation where using make for initialization, and manual sequential assignment can lead to any deterministic behavior desirable -- if you're using a map literal with potentially overlapping key expressions, then you should just accept that the results may vary. Is it really worth expanding the spec?

Dan Kortschak

unread,
Jan 26, 2013, 2:31:46 PM1/26/13
to Jan Mercl, minux, roger peppe, golang-nuts
Again, I don't really understand the complication.

Changing the snippet I used previously to this shows this http://play.golang.org/p/aDHPFgOuVv

Here you get the same behaviour shown with the map literal construction form, but with the added benefit that you see explicitly when initialisation of the map must be required. Translating this in the literal construction, we have three phases rather than the normal two for assignment:

1. map initialisation (else step 2 would panic).
2. recursive evaluation of the rhs in left to right order
3. assignment of the evaluated values to the lhs in left to right order

Am I missing something here?
> --
>
>

minux

unread,
Jan 26, 2013, 2:38:34 PM1/26/13
to Kevin Gillette, golan...@googlegroups.com, roger peppe, Jan Mercl
On Sun, Jan 27, 2013 at 2:52 AM, Kevin Gillette <extempor...@gmail.com> wrote:
I agree. or "In a map literal, the rules are undefined for resolving the result of using multiple expressions as keys which evaluate to be equal, or using identifiers which change during evaluation of the literal as keys or values. You should not depend on the observed behavior."
unless we randomize the order (like what we did for range over map), people
*will* depend on the observed behavior no matter how we say in the spec as
not every Go user read the spec and remember all the little warnings like this.

minux

unread,
Jan 26, 2013, 2:38:47 PM1/26/13
to Dan Kortschak, Jan Mercl, roger peppe, golang-nuts
On Sun, Jan 27, 2013 at 3:31 AM, Dan Kortschak <dan.ko...@adelaide.edu.au> wrote:
Again, I don't really understand the complication.
IMO, the major complexity come from line 27 of http://play.golang.org/p/inaHlQRYgQ.

Donovan Hide

unread,
Jan 26, 2013, 2:47:09 PM1/26/13
to minux, Dan Kortschak, Jan Mercl, roger peppe, golang-nuts
IMO, the major complexity come from line 27 of http://play.golang.org/p/inaHlQRYgQ.

It's true. The only unexpected fact from this whole discussion, given the spec, is that values get evaluated before keys in composite literal map assignment. I think the zero value of an int is just confusing the issue and allowing for the demonstration of some particularly obfuscated code :-)

minux

unread,
Jan 26, 2013, 2:53:05 PM1/26/13
to Donovan Hide, Dan Kortschak, Jan Mercl, roger peppe, golang-nuts

On Sun, Jan 27, 2013 at 3:47 AM, Donovan Hide <donov...@gmail.com> wrote:
IMO, the major complexity come from line 27 of http://play.golang.org/p/inaHlQRYgQ.
It's true. The only unexpected fact from this whole discussion, given the spec, is that values get evaluated before keys in composite literal map assignment. I think the zero value of an int is just confusing the issue and allowing for the demonstration of some particularly obfuscated code :-)
keys and values are symmetric and expressions with possible side-effects in them
are hoisted out and evaluated first before the actually map assignments occur.

line 25 and line 27 are examples for expression with side-effect for key and value,
respectively.

Dan Kortschak

unread,
Jan 26, 2013, 2:59:06 PM1/26/13
to itmi...@gmail.com, golan...@googlegroups.com, Jan Mercl, minux, roger peppe
That was my point.

On 27/01/2013, at 6:12 AM, "itmi...@gmail.com" <itmi...@gmail.com> wrote:

On Saturday, January 26, 2013 9:31:46 PM UTC+2, kortschak wrote:
Changing the snippet I used previously to this shows this http://play.golang.org/p/aDHPFgOuVv

I believe that is a convoluted way to say this: http://play.golang.org/p/KyVn2i--B5

a, aOk, b, bOk take zero-values upon initialization. If the assignment fails, those keep the zero-values.

minux

unread,
Jan 26, 2013, 3:01:10 PM1/26/13
to Kevin Gillette, golan...@googlegroups.com, roger peppe, Jan Mercl
On Sun, Jan 27, 2013 at 2:52 AM, Kevin Gillette <extempor...@gmail.com> wrote:
I agree. or "In a map literal, the rules are undefined for resolving the result of using multiple expressions as keys which evaluate to be equal, or using identifiers which change during evaluation of the literal as keys or values. You should not depend on the observed behavior."
please also consider this: http://play.golang.org/p/jg8yO6yFPg
my proposed specification can also explain this result (after changing map to slice/array).

The problem is not specific to map.

Jan Mercl

unread,
Jan 26, 2013, 3:05:36 PM1/26/13
to kortschak, golang-nuts, roger peppe, minux

V


On Jan 26, 2013 8:31 PM, "Dan Kortschak" <dan.ko...@adelaide.edu.au> wrote:
>
> Again, I don't really understand the complication.
>

> Changing the snippet I used previously to this shows this http://play.golang.org/p/aDHPFgOuVv
>

> Here you get the same behaviour shown with the map literal construction form, but with the added benefit that you see explicitly when initialisation of the map must be required. Translating this in the literal construction, we have three phases rather than the normal two for assignment:
>
> 1. map initialisation (else step 2 would panic).
> 2. recursive evaluation of the rhs in left to right order
> 3. assignment of the evaluated values to the lhs in left to right order
>
> Am I missing something here?
>

This IMO discusses the parts already worked out in this thread.

What it seems to miss is that a) the current behavior cannot be explained (well defined, deterministic) by the specs in some cases and b) those cases cannot be explained by any permutation of "insertion order" as evaluation side effects kick in - in an (yet another) unspecified way. Thus defining (just) the insertion order is not sufficient.

-j

minux

unread,
Jan 26, 2013, 3:05:54 PM1/26/13
to Kevin Gillette, golan...@googlegroups.com, roger peppe, Jan Mercl
On Sun, Jan 27, 2013 at 4:01 AM, minux <minu...@gmail.com> wrote:
On Sun, Jan 27, 2013 at 2:52 AM, Kevin Gillette <extempor...@gmail.com> wrote:
I agree. or "In a map literal, the rules are undefined for resolving the result of using multiple expressions as keys which evaluate to be equal, or using identifiers which change during evaluation of the literal as keys or values. You should not depend on the observed behavior."
please also consider this: http://play.golang.org/p/jg8yO6yFPg
gccgo and gc don't agree on this example.
my proposed specification can also explain this result (after changing map to slice/array).
my specification could only explain gccgo's result.

Jan Mercl

unread,
Jan 26, 2013, 3:09:47 PM1/26/13
to minux, Kevin Gillette, roger peppe, golan...@googlegroups.com


On Jan 26, 2013 9:01 PM, "minux" <minu...@gmail.com> wrote:
>
> The problem is not specific to map.

Agreed, my earlier proposal covers that.

-j

Dan Kortschak

unread,
Jan 26, 2013, 3:10:10 PM1/26/13
to minux, Jan Mercl, roger peppe, golang-nuts
I still don't understand how this is different to the normal assignment specification with the exception of the addition of an initialisation stage.

http://play.golang.org/p/l2sTng4ViB

Donovan Hide

unread,
Jan 26, 2013, 3:10:17 PM1/26/13
to minux, Kevin Gillette, golang-nuts, roger peppe, Jan Mercl
I was wrong :-(

Another obfuscated example:


All elements in an element list in a composite literal with side effects are evaluated first, in source order. After this evaluation any remaining elements are evaluated in source order.

Dan Kortschak

unread,
Jan 26, 2013, 3:13:54 PM1/26/13
to Donovan Hide, minux, Kevin Gillette, golang-nuts, roger peppe, Jan Mercl
OK, now I'm with it. Thanks.
--
 
 

minux

unread,
Jan 26, 2013, 3:22:40 PM1/26/13
to golang-nuts
Aha, it seems the order is indeed unspecified in the spec.


the only guarantee from the spec, as I understand it, is that
sub-expressions with possible side effects are evaluated in the
source code order.

all other orders are left unspecified.

I will propose a CL to include the map example into the example
below.

Donovan Hide

unread,
Jan 26, 2013, 3:34:34 PM1/26/13
to minux, golang-nuts
a := 1
f := func() int { a = 2; return 3 }
x := []int{a, f()}  // x may be [1, 3] or [2, 3]: evaluation order between a and f() is not specified
That is kind of scary! At least the playground does what you'd expect after this long thread :-)


Why is the order unspecified? Does it make it easier to writer the lexer or the compiler?

Dan Kortschak

unread,
Jan 26, 2013, 3:36:39 PM1/26/13
to minux, golang-nuts
Thanks minux. That is what I have been trying to get across.

minux

unread,
Jan 26, 2013, 3:49:07 PM1/26/13
to Donovan Hide, golang-nuts
On Sun, Jan 27, 2013 at 4:34 AM, Donovan Hide <donov...@gmail.com> wrote:

a := 1
f := func() int { a = 2; return 3 }
x := []int{a, f()}  // x may be [1, 3] or [2, 3]: evaluation order between a and f() is not specified
That is kind of scary! At least the playground does what you'd expect after this long thread :-)
me too. I was expecting the array/slice case to happens in order as there is an inherent order
between entries in an array or a slice, but there is no inherent order for map entries.

However, as I've said, gccgo and gc really don't agree with this example below (is it intentional?)  

$ cat y.go
package main
import "fmt"
func main() {
a := 1
f := func() int { a = 2; return 3 }
x := []int{a, f()} // x may be [1, 3] or [2, 3]: evaluation order between a and f() is not specified
fmt.Println(x)
}
$ go run y.go
[2 3]
$ gccgo y.go && ./a.out
[1 3]


Why is the order unspecified? Does it make it easier to writer the lexer or the compiler?
i'm not sure. From an implementor's standpoint, I expect the behavior of gccgo's
section 3.4, quoted below:
4. Gccgo lowers the parse tree. ..... [snip]
* When statements include expressions with side-effects at the top level, gccgo changes
those expressions into assignments to temporary variables and places the new assignments
before the statement. This implements Go’s rules about evaluation order within a single
statement. The expressions with side effects are function calls and send or receive expressions
on a channel.
)

anyway, i created https://codereview.appspot.com/7235044/, please comment.


take away lesson:
read the spec carefully again and again and again ... until you can recite it. ;-)

minux

unread,
Jan 26, 2013, 3:52:17 PM1/26/13
to Dan Kortschak, golang-nuts
On Sun, Jan 27, 2013 at 4:36 AM, Dan Kortschak <dan.ko...@adelaide.edu.au> wrote:
Thanks minux. That is what I have been trying to get across.
apologies for constantly misunderstood you, we should have arrived at the
final answer earlier.

will read the spec ten more times before joining similar discussion again :-)

Dan Kortschak

unread,
Jan 26, 2013, 3:54:07 PM1/26/13
to minux, golang-nuts
Careful, that sounds like a post-confessional Hail Mary penance.

Donovan Hide

unread,
Jan 26, 2013, 4:08:41 PM1/26/13
to Dumitru Ungureanu, Dan Kortschak, golang-nuts, minux
I guess the takeaway from this lengthy discussion is if you want to initialise/assign to your maps or slices with composite literals, evaluate your state-affecting functions in the correct order beforehand or don't depend on portability between compilers... 

Dan, looks like your multiple assignment is, in fact, possibly the safest way to do things :-)
Minux, the change to the spec makes sense to me, although the outcome of the map assignment depends on the unspecified outcome of the slice assignment :-)
Mitica, I'm scared :-)

Donovan Hide

unread,
Jan 26, 2013, 4:15:58 PM1/26/13
to Dumitru Ungureanu, golang-nuts, Dan Kortschak, minux
Actually, minux, the first line of the proposed spec example will always be 2:2, if it comes after the slice assignment:

minux

unread,
Jan 26, 2013, 4:25:29 PM1/26/13
to Donovan Hide, Dumitru Ungureanu, golang-nuts, Dan Kortschak
On Sun, Jan 27, 2013 at 5:15 AM, Donovan Hide <donov...@gmail.com> wrote:
Actually, minux, the first line of the proposed spec example will always be 2:2, if it comes after the slice assignment:
yes, f() is evaluated, and a == 2 before that line
that example mainly demonstrated that {a:1, a:2} has unspecified order of assignments.
it could be:
m[a] = 1
m[a] = 2

or, it could be:
m[a] = 2
m[a] = 1

this is to answer the OP's question of wether the result of this program well-defined.
http://play.golang.org/p/vz7LEIJSFR


Dan Kortschak

unread,
Jan 26, 2013, 4:27:05 PM1/26/13
to Donovan Hide, Dumitru Ungureanu, golang-nuts, minux
In every example I've translated from map literal initialisation and assignment to initialisation and the multiple assignment I see exactly the same outcome, so I don't think it's more safe. I (possibly) is less obfuscated, but even that is, I think, just a consequence of people thinking about literal initialisation and multiple assignment differently. I don't think they are, but my assembler is not adequate to be able to determine that.

The only safe way to do this is be explicit with order by not using literals or multiple assignment, thereby avoiding the specifically unspecified order behaviour.

Donovan Hide

unread,
Jan 26, 2013, 4:35:21 PM1/26/13
to Dan Kortschak, Dumitru Ungureanu, golang-nuts, minux
In every example I've translated from map literal initialisation and assignment to initialisation and the multiple assignment I see exactly the same outcome, so I don't think it's more safe. I (possibly) is less obfuscated, but even that is, I think, just a consequence of people thinking about literal initialisation and multiple assignment differently. I don't think they are, but my assembler is not adequate to be able to determine that.

It will be interesting to look at what the output of the new exp/ssa package will be for composite literal assignment compared to multiple assignment. Should be easier than diff-ing assembly :-)

minux

unread,
Jan 26, 2013, 4:39:46 PM1/26/13
to Donovan Hide, Dan Kortschak, Dumitru Ungureanu, golang-nuts

On Sun, Jan 27, 2013 at 5:35 AM, Donovan Hide <donov...@gmail.com> wrote:
In every example I've translated from map literal initialisation and assignment to initialisation and the multiple assignment I see exactly the same outcome, so I don't think it's more safe. I (possibly) is less obfuscated, but even that is, I think, just a consequence of people thinking about literal initialisation and multiple assignment differently. I don't think they are, but my assembler is not adequate to be able to determine that.

It will be interesting to look at what the output of the new exp/ssa package will be for composite literal assignment compared to multiple assignment. Should be easier than diff-ing assembly :-)
now i think if we can have a compiler option to randomly reorder all sub-expressions
with unspecified order of evaluation with each other just to flush out this sort of bugs.
and preferably "go vet" should be aware of this sort of problem.

Donovan Hide

unread,
Jan 26, 2013, 4:44:31 PM1/26/13
to Dumitru Ungureanu, golang-nuts

I think, from the tests, that is very well defined: http://play.golang.org/p/dZDgIdi-Jr

From the map or slice literal, it results a code block, where functions are evaluated first, in source code order, closure style, then the variables.

You are confirming what we've spent this thread trying to prove. But according, to the spec, as minux discovered, you cannot depend on this being the case:


Compare your example with:

a := 1
f := func() int { a = 2; return 3 }
x := []int{a, f()}  // x may be [1, 3] or [2, 3]: evaluation order between a and f() is not specified
emphasis added :-)

Donovan Hide

unread,
Jan 26, 2013, 4:47:24 PM1/26/13
to minux, Dan Kortschak, Dumitru Ungureanu, golang-nuts
now i think if we can have a compiler option to randomly reorder all sub-expressions
with unspecified order of evaluation with each other just to flush out this sort of bugs.
and preferably "go vet" should be aware of this sort of problem.

I wonder how many people have depended on this? I could imagine global counters might have been implemented in a very similar way to the incr() function...

Either that or change the spec to the observed gc behaviour and modify gccgo to match?   

Dan Kortschak

unread,
Jan 26, 2013, 7:31:22 PM1/26/13
to itmi...@gmail.com, golan...@googlegroups.com, Dumitru Ungureanu
This is why it says it's not specified. So people don't do experiments to find out how the implementation behaves. In this case, make the code state the order.

On 27/01/2013, at 10:31 AM, "itmi...@gmail.com" <itmi...@gmail.com> wrote:

As a general rule (even though the "evaluation order is not specified"), it looks like the literal keys are evaluated first while the variable keys are evaluated last, leaving functions, methods etcetera somewhere in between.

Dan Kortschak

unread,
Jan 28, 2013, 12:03:58 AM1/28/13
to golan...@googlegroups.com
This however, is confusing.

http://play.golang.org/p/Z2aaevAIbN

The outcome of this assignment requires that the index evaluation of the second lhs term (specified as happening in phase one) happens after the assignment of the first lhs term (specified as happening in phase two).

I would expect the output to be map[0:2] rather than map[1:2 0:1].

Jan Mercl

unread,
Jan 28, 2013, 2:38:56 AM1/28/13
to Dan Kortschak, golan...@googlegroups.com
I think you're right and it's a bug. Can you please fill an issue? (If
not already reported or fixed at tip, of course)

-j

Dan Kortschak

unread,
Jan 28, 2013, 6:06:51 AM1/28/13
to itmi...@gmail.com, golan...@googlegroups.com
On Mon, 2013-01-28 at 02:20 -0800, itmi...@gmail.com wrote:
> If you expect the first lhs element to take the value of the second
> rhs
> element, that would break the order of parallel assignment, where
> there has
> to be a 1:1 match for the number of elements.

What? (See below).

> I see your example as this:
>
> m[0] = 1
>
> m[m[0]] = 2

Which is not how the spec defines the behaviour:

The assignment proceeds in two phases. First, the operands of index
expressions and pointer indirections (including implicit pointer
indirections in selectors) on the left and the expressions on the right
are all evaluated in the usual order. Second, the assignments are
carried out in left-to-right order.

This means that, long windedly, the correct interpretation is:

// Phase one:

k1 = 0

k2 = m[0] // == 0 since m is empty.

// Phase two (left to right):

m[0] = 1 // k1

m[0] = 2 // k2

// Outcome:
// m == map[0:2]


This is apparently what happens with slices:
http://play.golang.org/p/4vRaPGs63w

> which accounts for the map[1:2 0:1] result.
>
Sure, but that behaviour is wrong.

John Asmuth

unread,
Jan 28, 2013, 10:54:25 AM1/28/13
to golan...@googlegroups.com, itmi...@gmail.com
x, y = y, x

is not the same as either

x = y
y = x

or 

y = x
x = y

On Monday, January 28, 2013 5:20:49 AM UTC-5, itmi...@gmail.com wrote:
On Monday, January 28, 2013 7:03:58 AM UTC+2, kortschak wrote:
The outcome of this assignment requires that the index evaluation of the second lhs term (specified as happening in phase one) happens after the assignment of the first lhs term (specified as happening in phase two). 

I would expect the output to be map[0:2] rather than map[1:2 0:1]. 

If you expect the first lhs element to take the value of the second rhs element, that would break the order of parallel assignment, where there has to be a 1:1 match for the number of elements.

I see your example as this:

m[0] = 1
m[m[0]] = 2

which accounts for the map[1:2 0:1] result.

Mitică

Dan Kortschak

unread,
Jan 28, 2013, 2:36:35 PM1/28/13
to golan...@googlegroups.com
Your interpretation missed the full stop. The assignment occurs in two phases. The evaluation of index expressions etc is carried out in the usual order, THEN the assignments occur. The evaluation of any entry in a new map will be to the zero vaue.

On 28/01/2013, at 10:20 PM, "itmi...@gmail.com" <itmi...@gmail.com> wrote:

> False. That only happens in the case of an *actual* k2 variable declaration.
> m[0] is the one being evaluated, and the order by which it's evaluated, is not specified... according to the "usual order".

chris dollin

unread,
Jan 28, 2013, 3:12:20 PM1/28/13
to itmi...@gmail.com, golan...@googlegroups.com
On 28 January 2013 19:51, <itmi...@gmail.com> wrote:
> On Monday, January 28, 2013 9:36:35 PM UTC+2, kortschak wrote:
>>
>> Your interpretation missed the full stop.
>
>
> Here is where you don't seem to follow, actually.
>
> This whole thread revolves around the fact that the evaluation phase may
> have events happening in an unspecified order:
>
> http://golang.org/ref/spec#Order_of_evaluation
>>
>> For example, in the 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.

Nothing that all of the evaluations happen before
any of the assignments.

http://golang.org/ref/spec#Assignments

(para 6, not counting the block-quoted code thingies.)

Chris

--
Chris "allusive" Dollin

Dan Kortschak

unread,
Jan 28, 2013, 3:13:34 PM1/28/13
to itmi...@gmail.com, golan...@googlegroups.com
Yes - apart from the the fact that the issue I've raised here is only tangentially related to the rest of the thread - but *In two phases*. This is something you avoid dealing with in your quoting of the order of evaluation without the context of rhs evaluation and lhs assignment (read and understand the tuple assignment section of the spec).

Dan Kortschak

unread,
Jan 28, 2013, 4:17:17 PM1/28/13
to itmi...@gmail.com, golan...@googlegroups.com
And yet, golang.org/issue/4620.

On Mon, 2013-01-28 at 12:49 -0800, itmi...@gmail.com wrote:
> with none of the "two phase" guarantees that you're looking for.


John Nagle

unread,
Jan 28, 2013, 4:47:29 PM1/28/13
to golan...@googlegroups.com
On 1/24/2013 9:45 AM, Donovan Hide wrote:
> This gets rejected:
>
> http://play.golang.org/p/ll-9Amb6Bk

Right. That has a "const" as the map key.

If you try a "var" as the map key of a const literal map,

http://play.golang.org/p/2Ull2Ib4L2

you get "prog.go:12: const initializer must be constant".

So, while keys can't be references or slices,
but variables are currently allowed for non-const maps.

What does it mean when the key of a literal map is a variable?
Is the key the reference to the variable, or the contents of it?
When is the key evaluated? What about scope issues? Goroutines?
Should that be allowed?

There probably should be a section under Composite Literals
along the lines of "For map literals the following rules apply:"

John Nagle

Donovan Hide

unread,
Jan 28, 2013, 5:16:23 PM1/28/13
to John Nagle, golang-nuts
> This gets rejected:
>
> http://play.golang.org/p/ll-9Amb6Bk

   Right.  That has a "const" as the map key.

If you try a "var" as the map key of a const literal map,

http://play.golang.org/p/2Ull2Ib4L2

   you get "prog.go:12: const initializer must be constant".

   So, while keys can't be references or slices,
but variables are currently allowed for non-const maps.

You just can't have const maps,keys,arrays,slices or structs:


That's why you got the error. Consts are evaluated at compile time, so it would require some C++ type magic to stop you changing the reference types at run-time.  


I definitely agree the spec could do with some more complete examples. 

roger peppe

unread,
Jan 29, 2013, 3:35:25 AM1/29/13
to John Nagle, golang-nuts
On 28 January 2013 21:47, John Nagle <na...@animats.com> wrote:
> What does it mean when the key of a literal map is a variable?
> Is the key the reference to the variable, or the contents of it?

the contents of the variable.

> When is the key evaluated? What about scope issues? Goroutines?

it's evaluated when the map is created. there are no additional
scope or goroutine issues here.

the only question i'd still like an answer for is: is this code

map[int]int{a: 1, a: 2}

always equivalent to this code?

func() map[int]int{
m := make(map[int]int)
m[a] = 1
m[a] = 2
return m
}()

or might some Go compiler decide to do the map initialisation
in the opposite (or random) order?

Dan Kortschak

unread,
Jan 29, 2013, 3:49:37 AM1/29/13
to roger peppe, John Nagle, golang-nuts
The spec seems remarkably quiet on that. Worth a bug report to at least pin it down.
Reply all
Reply to author
Forward
0 new messages