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.
This is a "you had one job" level language defect. If there are excuses that allow Go to pretend that structs with duplicate member names are well formed, good programming language designers would reject them in horror and specify how the compiler should actively look for this type of mistake, without wasting effort to look for such excuses.
Over the course of ~10 years of writing Go, my ratio of "embedding a struct" to "regretting embedding a struct" is nearly 1:1.
I do not embed structs anymore. It is almost always a mistake. I would confidently place it in the "you should be required to import 'unsafe' to use this feature" bin.
Using struct embedding for pure data to implement discriminated unions is fine, better than MarshalJSON() that is lost on a type definition. Using it to save typing, or going crazy with it (I consider embedding two things going crazy) is bad.
I think there are a handful of cases where it is a nice-to-have and would be sad if it was removed in a hypothetical Go 2. Making a utility wrapper struct that overrides a method or adds new helper methods while keeping the rest of the interface is the most common example, though there are also some JSON spec type examples which are a little more esoteric. However, you need to be mentally prepared to switch to the boilerplate version as soon as things start getting hairy.
But yes, for anything more complicated I have generally regretted trying to embed structs. I think requiring "unsafe" is a bit too strong, but I think the syntax should've been uglier / more in-your-face to discourage its use.
I think embedding structs would be way more useful if a) there would be properties on interfaces and b) there would be generic methods available.
As long as these two aren't there, embedding structs is literally identical to dispatching methods, and can't be used for anything else due to lack of state management through it. You have to manage the states externally anyways from a memory ownership perspective.
If you need to grab a particular struct's version of the data, you can via `opts.BarService.URL` or `opts.FooService.URL`: https://go.dev/play/p/MUSYJhmoC2D
Still worth being careful, but it can be useful when you have a set of common fields that everything of a certain group will have (such as a response object with basic status, debug info, etc. and then additional data based on the particular struct). I don't know why they let you embed multiple layers and multiple objects though. I've never gotten value out of anything but a "here's a single set of common fields struct embedding".
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 just have a hashmap with them all.
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 wonder why they don’t do this for all levels. How deep could this even be in practice for it to matter? You could just have a hashmap with them all.
While it may seem questionable for fields; it applies to methods too and is potentially more useful as a way to override them when doing struct embedding but wanting to preserve an interface.
Spec: https://go.dev/ref/spec#Selectors
> x.f resolves to the field/method at the shallowest depth in T. If there isn’t exactly one at that depth, it’s illegal.
Embedding promotes fields; on name collisions the shallowest wins. So `opts.URL` is `FooService.URL` (depth 1), not `BarConnectionOptions.URL` (depth 2).
That something is clearly specced doesn’t imply all developers actively know it.
Even given that it compiles, I wouldn’t exclude it being a runtime error.
But the big problem isn’t that it behaves as advertised, it’s that it is way too easy to write opts.URL where you mean opts.Bar.URL. Auto-complete will happily compete the wrong thing for you.
Maybe I see it differently, but it made sense: embeding works only at 0 depth, it's like a macro to access rapidly the fields of the embedded struct, it doesn't go beyond that,there is no inheritance.
When embedding BarService, the field being embedded is BarConnectionOptions
It's my common code review comment to the beginners to not embed structs. There's rarely anything to be gained by doing so. The only use case I found to be useful is to embed something like:
where I get to override only a part of a bigger interface at a time and have the embedding take care of the rest by saying panic("unimplemented") to satisfy the interface.
Making the compiler fail that would indeed be nice. A clear example of a language feature that makes sense when there's a single developer, but in a code base with hundreds of developers, this could easily break something without anyone noticing.
I have code that wouldn't work without embedding. Basically, I have a type that annotates any AWS paginate and is capable of streaming all the results as a sequence. It embeds the original client so you get all the functionality of the client, but it also wraps the functions that support a pagination. I can't think of an easier or clearer way to do it.
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.
After that time, I did not use json or bson Unmarshal to cast anymore. Any transform/converter function of me is manual directly. And thanks for LLM, with the their help, I only need to review the function, so no more typing.
> Embedding types introduces the problem of name conflicts but the rules to resolve them are simple. First, a field or method X hides any other item X in a more deeply nested part of the type. If log.Logger contained a field or method called Command, the Command field of Job would dominate it.
> Second, if the same name appears at the same nesting level, it is usually an error; it would be erroneous to embed log.Logger if the Job struct contained another field or method called Logger. 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.
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.
At risk of being excessively sassy this looks like a case of wanting the ergonomics of multiple inheritance without fully grappling with the complexities or implications of it.
Normally you wouldn’t contrive to use embedded struct fields in this way. And you can’t have the same kind of composition with methods - it’s a compiler error:
My personal conspiracy is that Golang is an epic prank.
Make a language that's really good in some ways and just horrible in other ways for no reason whatsoever.
So that when it's critics point out contradictory features like embedding, it's defenders can be the ultimate troll and say things like "but, actually, it's a simple language because it doesn't have while loops".
It's the best explanation I have for some of the cognitive dissonance surrounding the language design.
Is it possible that it's like every other language, with flaws and tradeoffs that don't always make sense to everyone? Why make it more complicated than that?
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.
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:
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):
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.
I don't really use golang all that much and even I was confused about OP's point because of the different depths.
I feel like sometimes people just want to complain.
This is a "you had one job" level language defect. If there are excuses that allow Go to pretend that structs with duplicate member names are well formed, good programming language designers would reject them in horror and specify how the compiler should actively look for this type of mistake, without wasting effort to look for such excuses.
Over the course of ~10 years of writing Go, my ratio of "embedding a struct" to "regretting embedding a struct" is nearly 1:1.
I do not embed structs anymore. It is almost always a mistake. I would confidently place it in the "you should be required to import 'unsafe' to use this feature" bin.
Using struct embedding for pure data to implement discriminated unions is fine, better than MarshalJSON() that is lost on a type definition. Using it to save typing, or going crazy with it (I consider embedding two things going crazy) is bad.
I think there are a handful of cases where it is a nice-to-have and would be sad if it was removed in a hypothetical Go 2. Making a utility wrapper struct that overrides a method or adds new helper methods while keeping the rest of the interface is the most common example, though there are also some JSON spec type examples which are a little more esoteric. However, you need to be mentally prepared to switch to the boilerplate version as soon as things start getting hairy.
But yes, for anything more complicated I have generally regretted trying to embed structs. I think requiring "unsafe" is a bit too strong, but I think the syntax should've been uglier / more in-your-face to discourage its use.
(Fellow 10+ years Go user.)
I think embedding structs would be way more useful if a) there would be properties on interfaces and b) there would be generic methods available.
As long as these two aren't there, embedding structs is literally identical to dispatching methods, and can't be used for anything else due to lack of state management through it. You have to manage the states externally anyways from a memory ownership perspective.
If you need to grab a particular struct's version of the data, you can via `opts.BarService.URL` or `opts.FooService.URL`: https://go.dev/play/p/MUSYJhmoC2D
Still worth being careful, but it can be useful when you have a set of common fields that everything of a certain group will have (such as a response object with basic status, debug info, etc. and then additional data based on the particular struct). I don't know why they let you embed multiple layers and multiple objects though. I've never gotten value out of anything but a "here's a single set of common fields struct embedding".
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 just have a hashmap with them all.
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 wonder why they don’t do this for all levels. How deep could this even be in practice for it to matter? You could just have a hashmap with them all.
While it may seem questionable for fields; it applies to methods too and is potentially more useful as a way to override them when doing struct embedding but wanting to preserve an interface.
It performs a breadth-first search, not a depth-first search.
Who's actually expecting `xyz.com` here?
Spec: https://go.dev/ref/spec#Selectors > x.f resolves to the field/method at the shallowest depth in T. If there isn’t exactly one at that depth, it’s illegal.
Embedding promotes fields; on name collisions the shallowest wins. So `opts.URL` is `FooService.URL` (depth 1), not `BarConnectionOptions.URL` (depth 2).
That something is clearly specced doesn’t imply all developers actively know it.
Even given that it compiles, I wouldn’t exclude it being a runtime error.
But the big problem isn’t that it behaves as advertised, it’s that it is way too easy to write opts.URL where you mean opts.Bar.URL. Auto-complete will happily compete the wrong thing for you.
I'm surprised this wasn't in the recent post submitted here: https://blog.habets.se/2025/07/Go-is-still-not-good.html
It's a one of a few rough edges in Go.
You could add it as a comment there, or on the HN discussion?
Maybe I see it differently, but it made sense: embeding works only at 0 depth, it's like a macro to access rapidly the fields of the embedded struct, it doesn't go beyond that,there is no inheritance.
When embedding BarService, the field being embedded is BarConnectionOptions
Huh my IDE linter spits out warnings about this. Not sure which extension does it.
It's my common code review comment to the beginners to not embed structs. There's rarely anything to be gained by doing so. The only use case I found to be useful is to embed something like:
where I get to override only a part of a bigger interface at a time and have the embedding take care of the rest by saying panic("unimplemented") to satisfy the interface.Struct embedding is sugar for
So it would resolve myFoo.MyType1.url over myFoo.MyType2.NestedType.urlMaking the compiler fail that would indeed be nice. A clear example of a language feature that makes sense when there's a single developer, but in a code base with hundreds of developers, this could easily break something without anyone noticing.
I have code that wouldn't work without embedding. Basically, I have a type that annotates any AWS paginate and is capable of streaming all the results as a sequence. It embeds the original client so you get all the functionality of the client, but it also wraps the functions that support a pagination. I can't think of an easier or clearer way to do it.
Am I the only one who found the described behavior to be intuitively correct? I did expect it to print "abc.com".
I don’t write Go at all but given the first example, also expected this.
I was very surprised that either example compiled, though.
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.
I'm confused at the responses saying this intuitive. It's like saying:
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.
Yeah it makes sense to me as well.
What is the intuition for that?
The same issue with embbedding of field had reflect tag like json or bson ...
After that time, I did not use json or bson Unmarshal to cast anymore. Any transform/converter function of me is manual directly. And thanks for LLM, with the their help, I only need to review the function, so no more typing.
That’s actually crazy. Why is this even a feature?
Because it's useful.
https://go.dev/doc/effective_go#embedding
> Embedding types introduces the problem of name conflicts but the rules to resolve them are simple. First, a field or method X hides any other item X in a more deeply nested part of the type. If log.Logger contained a field or method called Command, the Command field of Job would dominate it.
> Second, if the same name appears at the same nesting level, it is usually an error; it would be erroneous to embed log.Logger if the Job struct contained another field or method called Logger. 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.
When you create a struct with another embedded struct (possibly from other package):
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.
See how it's used in the standard library io types, it makes for quite nice composition: https://go.googlesource.com/go/+/refs/heads/master/src/io/io...
At the very least, the Go authors have been convinced this should be a feature since the Plan 9 C dialect[1].
[1] http://doc.cat-v.org/plan_9/4th_edition/papers/comp, look for “anonymous structure or union” and note that a (different) part of that extension has since been standardized.
Because
is convenient.It seems to come from a Plan 9 C idiom that GCC even has an extension for it.
https://gcc.gnu.org/onlinedocs/gcc-5.3.0/gcc/Unnamed-Fields....
At risk of being excessively sassy this looks like a case of wanting the ergonomics of multiple inheritance without fully grappling with the complexities or implications of it.
Normally you wouldn’t contrive to use embedded struct fields in this way. And you can’t have the same kind of composition with methods - it’s a compiler error:
https://go.dev/play/p/r04tPta1xZo
So the whole article is basically about using the language in a way you normally would ever do.
IMHO it should be a compiler error. This is just so loose... a wheel fell off.
My personal conspiracy is that Golang is an epic prank.
Make a language that's really good in some ways and just horrible in other ways for no reason whatsoever.
So that when it's critics point out contradictory features like embedding, it's defenders can be the ultimate troll and say things like "but, actually, it's a simple language because it doesn't have while loops".
It's the best explanation I have for some of the cognitive dissonance surrounding the language design.
Is it possible that it's like every other language, with flaws and tradeoffs that don't always make sense to everyone? Why make it more complicated than that?
> just horrible in other ways for no reason whatsoever
I bet the reasons were very mundane: initial project scope, deadlines, performance review cycle. "This simplest thing that could possibly work", etc.
As with most small-team languages, it was built mostly to solve the problems that its initial author had in front of them.
Hello from another Matt Hall! Interesting post, although I don't do much Golang.
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.
> I hope the feature mentioned in the article will cause a compiler error.
Read the article. It won't.
At best you can perhaps find a linter that'll report it?
> However, I wouldn't use this approach when writing my own code.
You might use it by accident.
I tested the tool more thoroughly using a strict method to review the code, but it couldn't find the problems mentioned in the article.
golangci-lint run --enable-all --max-issues-per-linter=0 --max-same-issues=0
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.