Be Careful with Go Struct Embedding
18 comments
·September 21, 2025tymscar
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.
dgl
> 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.
digianarchist
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.
bilbo-b-baggins
Huh my IDE linter spits out warnings about this. Not sure which extension does it.
tymscar
That’s actually crazy. Why is this even a feature?
mananaysiempre
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.
jrockway
Because
type Foo struct {
sync.Mutex
whatever string
}
var foo Foo
foo.Lock()
foo.whatever = 42
foo.Unlock()
is convenient.sethammons
almost always, the recommendation is to not embed your mutex; give it a name.
foo.mu.Lock()
This way you don't expose your primitives, preventing poor usage from causing a deadlock. Generally you don't want the user of your struct to have to know when or when to not lock.
thrill
Hmm, never realized the convenience came this way. Seems the compiler could emit a warning if two equal depth names might cause confusion, which could be ignored if acceptable.
echelon
It's dangerous. This is awful.
Any coding construct that can cause defects is an antipattern. Your language should discourage defects by design. Especially if the faults crop up at runtime.
This struct field dereferencing is like NULLs and "goto".
Language design that is anti-defect yet ergonomic include the modern Option<T> and Result<T, E> as seen in languages such as Swift and Rust, with first class destructuring that doesn't make it painful to use. They're almost impossible to misuse, yet feel convenient instead of frictionful. Rust's sum types and matching are another set of examples. Hopefully these patterns spread to more languages, because they're safe and convenient.
dgl
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...
mananaysiempre
I’m sympathetic to parts of the Go design philosophy, but the only thing that comes to mind looking at this is “damn, that’s some awkward (nominal-looking) syntax for (structural) intersection types”.
(It also feels to me that this sort of anonymous embedding is materially different for interfaces vs structs, though I admit that from a type-theoretic perspective it’s not.)
mikepurvis
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.
gdbsjjdn
In most cases people just want any inheritance, this is the backwards way the Golang devs decided to implement it based on their 80s view of programming languages.
metadat
IMHO it should be a compiler error. This is just so loose... a wheel fell off.
ShroudedNight
A wheel is generous. This seems more like inviting the computing equivalent of spilling twenty thousand tons of crude into the sea, which then promptly catch fire.
linhan_dot_dev
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.
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".