Go misfeature?

727 views
Skip to first unread message

Harald Fuchs

unread,
Oct 25, 2017, 4:51:08 AM10/25/17
to golan...@googlegroups.com
I got bitten by what I'd say is a misfeature in Go:

type T1 struct {
T2
T3
}

type T2 struct {
T4
}

type T3 struct {
foo int
}

type T4 struct {
foo int
}

func main() {
t1 := T1{
T2{
T4{
1,
},
},
T3{
2,
},
}
fmt.Printf("foo=%d\n", t1.foo)
}

This prints "foo=2" which surprised me because I was unaware that T1
had got another foo via T3 (with less composition depth).

If I put T4 directly into T1, Go would detect the ambiguity of t1.foo
(because the composition depth is the same).

Lutz Horn

unread,
Oct 25, 2017, 5:03:00 AM10/25/17
to golan...@googlegroups.com
> This prints "foo=2" which surprised me because I was unaware that T1
> had got another foo via T3 (with less composition depth).
>
> If I put T4 directly into T1, Go would detect the ambiguity of t1.foo
> (because the composition depth is the same).

There is no ambiguity because `T4.foo` is not promoted to a field of
`T1`. `T4.foo` *is* a promoted field of `T2` but promotion is not
transitive.

see https://play.golang.org/p/GFgJGTSGGd

Read about this on:

* https://golang.org/ref/spec#Struct_types
* https://golang.org/ref/spec#Selectors

Lutz

Axel Wagner

unread,
Oct 25, 2017, 8:11:55 AM10/25/17
to Lutz Horn, golang-nuts
On Wed, Oct 25, 2017 at 11:02 AM, Lutz Horn <lutz...@posteo.de> wrote:
This prints "foo=2" which surprised me because I was unaware that T1
had got another foo via T3 (with less composition depth).

If I put T4 directly into T1, Go would detect the ambiguity of t1.foo
(because the composition depth is the same).

There is no ambiguity because `T4.foo` is not promoted to a field of `T1`.

`T4.foo` *is* a promoted field of `T2` but promotion is not transitive.

see https://play.golang.org/p/GFgJGTSGGd

Read about this on:

https://golang.org/ref/spec#Struct_types
* https://golang.org/ref/spec#Selectors

Lutz


--
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+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Ian Lance Taylor

unread,
Oct 25, 2017, 9:45:35 AM10/25/17
to Harald Fuchs, golang-nuts
What is the misfeature, though? You gave us a clear explanation of
what you did and what happened; thanks very much for doing that. But
you didn't say what you expected to happen instead.

I'll speculate that perhaps you would prefer a compilation error due
to an ambiguous field. Not doing that was an explicit decision.
Consider a struct in package P1 that embeds two structs, one from
package P2 and one from P3. Suppose the struct in P3 embeds another
struct from P4. Suppose that all four packages are in different
github repos maintained by different people; recall that one of the
explicit goals of the Go language is to support programming at scale.
With this organization, package P1 can refer directly to fields
defined in P2 and P3. We want it to be possible for P4 to add a
field, a field that might happen to duplicate a field in P2--in this
example, P2 and P4 might not even know about each other at all--and we
want that to happen without breaking P1. Hope that makes sense.

Ian

Bakul Shah

unread,
Oct 25, 2017, 10:08:39 AM10/25/17
to Ian Lance Taylor, Harald Fuchs, golang-nuts
Thus can create some subtle errors. Suppose T3’s field was named bar.
And the above example will print 1 for t1.foo. Now change T3 field to foo.
And t1.foo will be 2.

Harald Fuchs

unread,
Oct 25, 2017, 10:11:50 AM10/25/17
to golan...@googlegroups.com
Yes, that's what I would have expected (sorry for not mentioning it).

> Not doing that was an explicit decision.
> Consider a struct in package P1 that embeds two structs, one from
> package P2 and one from P3. Suppose the struct in P3 embeds another
> struct from P4. Suppose that all four packages are in different
> github repos maintained by different people; recall that one of the
> explicit goals of the Go language is to support programming at scale.
> With this organization, package P1 can refer directly to fields
> defined in P2 and P3. We want it to be possible for P4 to add a
> field, a field that might happen to duplicate a field in P2--in this
> example, P2 and P4 might not even know about each other at all--and we
> want that to happen without breaking P1. Hope that makes sense.

For us, it *did* break P1 in the sense that this silently changing its
semantics. And how about the following?

type P1 struct {
P2
P3
}

type P2 struct {
P4
}

type P3 struct {
P5
}

type P4 struct {
foo int
}

type P5 struct {
// foo int
}

P2 and P3 don't know each other. P1 breaks if P5 gets another foo.
It's almost the same (except composition depth), isn't it?

Ian Lance Taylor

unread,
Oct 25, 2017, 10:18:11 AM10/25/17
to Harald Fuchs, golang-nuts
Yes. You are only safe from changes deeper in the type hierarchy if
you only reference fields defined without embedding in the types that
you embed directly.

Ian

T L

unread,
Oct 25, 2017, 5:14:33 PM10/25/17
to golang-nuts

The full selector path of T4.foo for T1 is T1.T2.T4.foo,
The full selector path of T3.foo for T1 is T1.T3.foo.
The shortest one will suppress the longer one.
If there are more than two shortest ones, then they collide, none of them will be promoted.
 

Harald Fuchs

unread,
Oct 26, 2017, 3:20:47 AM10/26/17
to golan...@googlegroups.com
Understood.

Note to self: when accessing things with a composition depth >1, Thou
shalt be explicit.

Thanks for the explanation!

Reply all
Reply to author
Forward
0 new messages