Live data from Hacker News

Be careful with Go struct embedding

mattjhall.co.uk

71–80 of 101 posts

Re: Be careful with Go struct embedding

#71
post #7

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…

It performs a breadth-first search, not a depth-first search.

Re: Be careful with Go struct embedding

#72
post #53
post #45

Earlier 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?

If there were no duplicate URL field, say they were called FooURL and BarcoURL, you could access them as `opts.FooURL` and `opts.BarcoURL`. They are both fields of `opts`, via embedding. It's just that FooURL is embedded directly, while BarcoURL is coming from two levels of embedding.

Re: Be careful with Go struct embedding

#73

I 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.

This may be a language issue, but saying "I hope this causes a compilation failure" implies that you don't know if it does - this is probably why you got down voted, since the article very explicitly says that this doesn't cause compilation failure. You may have meant that you hoped it wouldn't do this - in that case you should have used a construction like "I'd hoped this would have caused a compilation failure".

Re: Be careful with Go struct embedding

#74
Some people in comments jump to the conclusion that Go allows conflicting names in embedded structs. It doesn't not – for the embedded structs of the same depth.

These 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

#75
post #7

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…

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).

Re: Be careful with Go struct embedding

#76

Earlier 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.

But the level of nesting isn't the same; only one of them has a direct URL field.

Re: Be careful with Go struct embedding

#77
post #2

That’s actually crazy. Why is this even a feature?

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 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

#78
post #69
post #59

Earlier 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"}, }

I'm making an analogy using simple assignments to show more clearly that it's actually counter-intuitive, I'm not claiming what you seem to be refuting, which at best seems to be beside the point.

Re: Be careful with Go struct embedding

#79
post #66

Earlier 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.

>And yes you should convert to OrderTypeA or OrderTypeB at the first opportunity

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

#80

Earlier 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.

You would get the same result if both URL fields were nested one level deeper. Being directly nested isn't the point.
Post reply on HN