Selector ambiguity

1,486 views
Skip to first unread message

Anthony Martin

unread,
Jun 19, 2011, 11:01:18 PM6/19/11
to golan...@googlegroups.com
What was the rationale for allowing selectors
with the same name but at different depths in
nested anonymous fields?

Looking at the following code, I would've
expected the top-level "x" selector of type
T4 to be ambiguous and hence invalid.

Thanks,
Anthony


package main

type T1 struct { x int }
type T2 struct { x int }
type T3 struct { T1 }

type T4 struct { T1; T3 }
type T5 struct { T1; T2 }

func main() {
_ = T1{}.x
_ = T2{}.x
_ = T3{}.x
_ = T4{}.x // why is this allowed?
_ = T5{}.x // ERROR "ambiguous DOT reference"
}

Fabian Reinartz

unread,
Jun 19, 2011, 11:12:43 PM6/19/11
to Anthony Martin, golan...@googlegroups.com
Probably this quote from 'Effective Go' answers your question somehow:

"Second, if the same name appears at the same nesting level, it is usually an error; [...] However, if the duplicate name is never mentioned in the program outside the type definition, it is OK. This qualification provides some protection against changes made to types embedded from outside; there is no problem if a field is added that conflicts with another field in another subtype if neither field is ever used."

Gustavo Niemeyer

unread,
Jun 19, 2011, 11:18:14 PM6/19/11
to Anthony Martin, golan...@googlegroups.com
> What was the rationale for allowing selectors
> with the same name but at different depths in
> nested anonymous fields?
>
> Looking at the following code, I would've
> expected the top-level "x" selector of type
> T4 to be ambiguous and hence invalid.

Indeed, you're right, and that's a bug. Would you mind to file an
issue about it?

--
Gustavo Niemeyer
http://niemeyer.net
http://niemeyer.net/blog
http://niemeyer.net/twitter

Anthony Martin

unread,
Jun 19, 2011, 11:21:12 PM6/19/11
to Gustavo Niemeyer, golan...@googlegroups.com
Gustavo Niemeyer <gus...@niemeyer.net> once said:
> Indeed, you're right, and that's a bug. Would you
> mind to file an issue about it?

I'm not sure it's a bug.
Look at test/fixedbugs/bug253.go

Anthony

Rob 'Commander' Pike

unread,
Jun 19, 2011, 11:28:20 PM6/19/11
to Gustavo Niemeyer, Anthony Martin, golan...@googlegroups.com

On Jun 20, 2011, at 1:18 PM, Gustavo Niemeyer wrote:

>> What was the rationale for allowing selectors
>> with the same name but at different depths in
>> nested anonymous fields?
>>
>> Looking at the following code, I would've
>> expected the top-level "x" selector of type
>> T4 to be ambiguous and hence invalid.
>
> Indeed, you're right, and that's a bug. Would you mind to file an
> issue about it?

It's not a bug. Working as intended, as the text from Effective Go explains.

-rob


Anthony Martin

unread,
Jun 19, 2011, 11:35:26 PM6/19/11
to Fabian Reinartz, golan...@googlegroups.com

This doesn't address my question but
the paragraph above this one does. :)

Thanks everyone,
Anthony

CrossWall

unread,
Jun 20, 2011, 12:40:50 AM6/20/11
to golan...@googlegroups.com
There is an implicity rule for embeded types.

the rule is Simple First.

so:
type T4 struct { T1; T3 }
the T3 wrap T1, so T1 is simpler.   Closer First.

type T5 struct { T1; T2 }
the T1 T2 's x is in equal level.

Steven Blenkinsop

unread,
Jun 20, 2011, 12:44:42 AM6/20/11
to Anthony Martin, golan...@googlegroups.com
On Sun, Jun 19, 2011 at 11:01 PM, Anthony Martin <al...@pbrane.org> wrote:
What was the rationale for allowing selectors
with the same name but at different depths in
nested anonymous fields?

I would be interested in knowing this as well. Effective Go doesn't really say why, but more what, which is pretty much what you get from the spec (it does explain the "its only an error if you use it rule", which is good). It is a very simple rule so that's a plus. Also, if some type 4 levels down adds a new field, it's not going to mess up a method that you specifically included another type to get.

However, I don't see it scaling well. As soon as you actually have to apply the rule beyond 3 levels, you're probably doing something wrong and should resolve it manually anyway. I also don't see how there could be an "inconsequential change" that alters the interface of one of the types you're embedding. If it's a field, it's probably not exported, and if it's a method, it's probably important, and after a certain point, intentional. Go types are supposed to be light weight, so it shouldn't come up in your own packages using the standard library. However, as dependencies increase, and people use embedding to implement things internally, I can see depths creeping up.

If I'm designing a type, I'd rather only have two levels that the users need to be concerned about: top level and embedded. Beyond that, it shouldn't matter to them how I implemented my type, and if it does, then their code is probably precarious, since a simple refactoring a few levels back could move a field or method up or down a level, and change the result. This would mean that constructing a type, you'd only have to worry about three levels: top level of your type, and the two levels of the anonymous fields. While I'm at it, I think it should be taupe.


Gustavo Niemeyer

unread,
Jun 20, 2011, 11:06:00 AM6/20/11
to Rob 'Commander' Pike, Anthony Martin, golan...@googlegroups.com
> It's not a bug. Working as intended, as the text from Effective Go explains.

I can't see how it covers that case. It's obviously ambiguous.

What will this print:

type T1 struct { x int }
type T2 struct { x int }
type T3 struct { T1 }
type T4 struct { T1; T3 }

func main() {
t := T4{}
t.T1.x = 1
t.T3.x = 2
println(t.x)

Steven Blenkinsop

unread,
Jun 20, 2011, 11:14:09 AM6/20/11
to Gustavo Niemeyer, Rob 'Commander' Pike, Anthony Martin, golan...@googlegroups.com
On Monday, June 20, 2011, Gustavo Niemeyer <gus...@niemeyer.net> wrote:
>> It's not a bug. Working as intended, as the text from Effective Go explains.
>
> I can't see how it covers that case.  It's obviously ambiguous.
>
> What will this print:
>
> type T1 struct { x int }
> type T2 struct { x int }
> type T3 struct { T1 }
> type T4 struct { T1; T3 }
>
> func main() {
>         t := T4{}
>         t.T1.x = 1
>         t.T3.x = 2
>        println(t.x)
> }

By the spec, that will print "1".

> A selector f may denote a field or method f of a type T, or it may refer to a field or method f of a nested anonymous field of T. The number of anonymous fields traversed to reach f is called its depth in T. The depth of a field or method f declared in T is zero. The depth of a field or method f declared in an anonymous field A in T is the depth of f in A plus one.

> For a value x of type T or *T where T is not an interface type, x.f denotes the field or method at the shallowest depth in T where there is such an f. If there is not exactly one f with shallowest depth, the selector expression is illegal.

Gustavo Niemeyer

unread,
Jun 20, 2011, 11:20:14 AM6/20/11
to Rob 'Commander' Pike, Anthony Martin, golan...@googlegroups.com
> I can't see how it covers that case.  It's obviously ambiguous.

I see, it's a matter of depth. It feels somewhat error prone still,
but it's not ambiguous.

Gustavo Niemeyer

unread,
Jun 20, 2011, 11:37:45 AM6/20/11
to Steven Blenkinsop, Rob 'Commander' Pike, Anthony Martin, golan...@googlegroups.com
> By the spec, that will print "1".

Yeah, the spec is clear, and Effective Go mentions it as well (above
the text quoted in this thread).

I can see why it's allowed. It'd be somewhat boring to have to pick
names based on what is embedded or not elsewhere. It still feels a
bit easier than one would like to have logic broken in strange ways,
though, since it is possible for a name to magically start referring
to something else unintendedly. That said, we haven't seen anyone
reporting that yet, so it feels like the pragmatic benefit is worth
the risk.

Russ Cox

unread,
Jun 20, 2011, 1:28:43 PM6/20/11
to Anthony Martin, golan...@googlegroups.com
On Sun, Jun 19, 2011 at 23:01, Anthony Martin <al...@pbrane.org> wrote:
> What was the rationale for allowing selectors
> with the same name but at different depths in
> nested anonymous fields?

So that you can define

type T struct {
U
x int
}

and not worry about whether U contains an 'x int' too.

Russ

Rob 'Commander' Pike

unread,
Jun 20, 2011, 5:36:45 PM6/20/11
to r...@golang.org, Anthony Martin, golan...@googlegroups.com

Or whether might acquire an 'x' in the future.

-rob

Gustavo Niemeyer

unread,
Jun 20, 2011, 5:45:02 PM6/20/11
to Rob 'Commander' Pike, r...@golang.org, Anthony Martin, golan...@googlegroups.com
> Or whether might acquire an 'x' in the future.

That's the tricky bit. Adding an x suddenly changes existing code to
a complete new meaning without notices. That's the only detail that
feels a bit unsettling, but the trade-off feels useful.

Hilco Wijbenga

unread,
Jun 20, 2011, 6:19:03 PM6/20/11
to Gustavo Niemeyer, Rob 'Commander' Pike, r...@golang.org, Anthony Martin, golan...@googlegroups.com
On 20 June 2011 14:45, Gustavo Niemeyer <gus...@niemeyer.net> wrote:
>> Or whether might acquire an 'x' in the future.
>
> That's the tricky bit.  Adding an x suddenly changes existing code to
> a complete new meaning without notices.  That's the only detail that
> feels a bit unsettling, but the trade-off feels useful.

Would it not make sense then to simply disallow duplicate names but
allow for some sort of alias system to distinguish between them?

Gustavo Niemeyer

unread,
Jun 20, 2011, 6:25:06 PM6/20/11
to Hilco Wijbenga, Rob 'Commander' Pike, r...@golang.org, Anthony Martin, golan...@googlegroups.com
> Would it not make sense then to simply disallow duplicate names but
> allow for some sort of alias system to distinguish between them?

Doesn't seem worth the trouble at this point. The current system
works in practice, and my worries are mostly theoretical.

Kyle Lemons

unread,
Jun 20, 2011, 6:28:50 PM6/20/11
to Hilco Wijbenga, Gustavo Niemeyer, Rob 'Commander' Pike, r...@golang.org, Anthony Martin, golan...@googlegroups.com
Would it not make sense then to simply disallow duplicate names but
allow for some sort of alias system to distinguish between them?

That would introduce a lot of added complexity without adding any functionality.  At this point in the language's life cycle, I think we're mostly looking to simplify things.  Even something like generics, while complex, could be added if it was demonstrably go-like and simplified the solution to common problems.

The current embedding+selector mechanism is exceedingly simple, as can be seen by the fact that everything you need to know about its behavior can fit in a sentence or two.  Adding "some sort of alias system" would almost surely complicate matters and require examples and discussion to make it clear.  The only way in which adding a new variable/function to a structure would change the selector would be if you were referring to a deeply nested field and added one in the middle or (more likely) you added the field/method locally to override.  In both cases, if this somehow causes unintended behavior (that's kinda the point of overriding), it might be because you're naming your fields things like X that have ambiguous/no meaning.

~K

a...@google.com

unread,
Jun 20, 2011, 7:51:16 PM6/20/11
to golan...@googlegroups.com, Rob 'Commander' Pike, Anthony Martin


On Tuesday, 21 June 2011 01:06:00 UTC+10, Gustavo Niemeyer wrote:
> It's not a bug. Working as intended, as the text from Effective Go explains.

I can't see how it covers that case.  It's obviously ambiguous.

What will this print:

To me the answer is obviously "1", but I'm pretty well-versed in Go. ;-)

A more important question is "Do people write Go code like this? And what do they expect of it?"

There's nothing like that in the standard library. In fact, I've never seen a "multiple-inheritance" scenario like this in real Go code.

Should we care that a synthetic, convoluted piece of Go code is hard to understand?

Andrew

Gustavo Niemeyer

unread,
Jun 21, 2011, 1:21:12 AM6/21/11
to golan...@googlegroups.com, Rob 'Commander' Pike, Anthony Martin
> To me the answer is obviously "1", but I'm pretty well-versed in Go. ;-)

I, on the other hand, should be more humble when pointing out to
people what's a bug or not, since clearly I'm still ignorant on parts
of the spec.

> A more important question is "Do people write Go code like this? And what do
> they expect of it?"
> There's nothing like that in the standard library. In fact, I've never seen
> a "multiple-inheritance" scenario like this in real Go code.

I was more concerned about people that don't want to write logic like
this, but do so unintendedly. You can hit such a situation with
single inheritance, with the simplest scenario being:

type T1 struct { x int }

type T2 struct { T1 }

Now, add x to T2, and you switch every t2.x call to mean something else.

Of course, this is precisely the case that the feature design was
intended to cover in the first place, and it is useful as such, so I
take this more as a "I should watch out when adding fields when
embedded types exist" than "the feature is broken".

Reply all
Reply to author
Forward
0 new messages