So I got curious and I looked at the compiler source code, and it does a depth-first search. The fascinating bit to me is that there is a consolidateMultiples function in go/src/go/types/lookup.go (lines 286-304) that detects when multiple embedded types at the same depth provide the same field name. I wonder why they don’t do this for all levels. How deep could this even be in practice for it to matter? You could ju…
Be careful with Go struct embedding
71–80 of 101 posts
Re: Be careful with Go struct embedding
#72Earlier quoted context omitted.
This may be intuitively correct, but to my mind it is architecturally wrong. A good language should not tolerate ambiguity and offer to guess which behavior is correct.
Why is it ambiguous though? The second URL is nested Are thy not accessed like opts.URL == abc.com and opts.BarConnectionOptions.URL == xyz.com what leads you think otherwise?
Re: Be careful with Go struct embedding
#73I like the Go language because it's straightforward and clear, even if it looks a bit plain. I hope the feature mentioned in the article will cause a compiler error. However, I wouldn't use this approach when writing my own code.
This comment unexpectedly received a few downvotes, which might be due to some misunderstanding. My point is as follows: 1. People choose Go for various reasons. 2. Beginners will encounter the issues mentioned in the article, so I hope there could be a feature that provides hints for newcomers. While the compilation errors might not be reasonable, at least the LSP/linter could provide some guidance.
Re: Be careful with Go struct embedding
#74These won't compile:
type Bad struct {
Name string
Name string
}
type A struct{ Name string }
type B struct{ Name string }
type C struct {
A
B
}
bad.Name() // compile error: other declaration of Name
c.Name() // compile error: ambiguous selector c.Name
The case in article is about field names of the different depth. Spec is very clear about this behavior, and it was intentional.One of the reasons why handling same field names is different at different nesting levels is to protect against changes in structs coming from external libraries. Or, better phrased, external structs should not dictate what names you're allowed to use in your own structs (so they have priority).
I.e. when you create a struct with another embedded struct (possibly from other package):
type Foo struct {
somepackage.Bar
URL string
}
you don't want to depend on whether Bar already has URL. Your depth level has higher priority.Even more, imagine in the future authors of `somepackage` decided to add URL to their struct and it suddendly started to break your code from being compiled.
I agree that behavior in the OP article example is confusing (and so is the code - do you want URL of Foo service or Bar service?). Yet, this behavior is intentional and documented.
As usual, it's a subtle tradeoff here. If this feature would be implemented differently (say, compile time error for all depth levels), we would see an article with rant on how external structure changes breaks compilation.
Re: Be careful with Go struct embedding
#75So I got curious and I looked at the compiler source code, and it does a depth-first search. The fascinating bit to me is that there is a consolidateMultiples function in go/src/go/types/lookup.go (lines 286-304) that detects when multiple embedded types at the same depth provide the same field name. I wonder why they don’t do this for all levels. How deep could this even be in practice for it to matter? You could ju…
Re: Be careful with Go struct embedding
#76Earlier quoted context omitted.
I think it’s something like: “The general rule is that I may access the direct embeds of my type anonymously.”
Ok I don’t think anyone disagrees with that. But there are two embedded structs, both with a URL field.
Re: Be careful with Go struct embedding
#77That’s actually crazy. Why is this even a feature?
type Foo struct {
somepackage.Bar
URL string
}
you don't want to depend on whether Bar already have URL. Your depth level has higher priority.Even more, imagine in the future authors of `somepackage` decided to add URL to their struct and it suddendly started to break your code from being compiled.
Example in the OP article is a corner case where this behavior is creating ambiguity, indeed. Yet, it's documented and intentional.
Re: Be careful with Go struct embedding
#78Earlier quoted context omitted.
I'm confused at the responses saying this intuitive. It's like saying: var x string x = "abc.com" x = "xyz.com" fmt.Println(x) will print abc.com and that's totally expected. The normal intuition would be that the latter operations or (re)definitions override the preceding ones.
It's not at all like saying that. The order of operations has nothing to do with it. opts := Options{ FooService: FooService{URL: "abc.com"}, BarService: BarService{ BarConnectionOptions: BarConnectionOptions{ URL: "xyz.com", }, }, } is equivalent to opts := Options{ BarService: BarService{ BarConnectionOptions: BarConnectionOptions{ URL: "xyz.com", }, }, FooService: FooService{URL: "abc.com"}, }
Re: Be careful with Go struct embedding
#79Earlier quoted context omitted.
> And yes you should convert to OrderTypeA or OrderTypeB at the first opportunity in domain code, Go can only downcast through interfaces so there's something missing to your approach to unions, isn't there?
The missing something is manually creating the structs, not casting.
How would you convert Order to OrderTypeA? You would need some other source to fill TypeAAttr1 and TypeAAttr2 with.
Re: Be careful with Go struct embedding
#80Earlier quoted context omitted.
Ok I don’t think anyone disagrees with that. But there are two embedded structs, both with a URL field.
But the level of nesting isn't the same; only one of them has a direct URL field.