Live data from Hacker News

Be careful with Go struct embedding

mattjhall.co.uk

21–30 of 101 posts

Re: Be careful with Go struct embedding

#21
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.

Re: Be careful with Go struct embedding

#22
post #12

Earlier quoted context omitted.

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

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

You can’t have ambiguous methods so the problem illustrated here fails at compile time for interfaces.

Re: Be careful with Go struct embedding

#23
post #2

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.

Re: Be careful with Go struct embedding

#24
post #3
post #2

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

Because type Foo struct { sync.Mutex whatever string } var foo Foo foo.Lock() foo.whatever = 42 foo.Unlock() is convenient.

I thought Go was all about being "simple" at the cost of convenience.

It is a bit ironic that this language that was was designed around "all of these features of other languages cause trouble, we will omit them" also has a bunch of features that cause trouble and get avoided.

Just to make my own stance clear: I like language features. I think this struct embedding feature looks pretty cool. But I also like interfaces and polymorphism. I think it's OK for a programming language to be powerful, and to put the onus on developers to not go too crazy with that power. And for that reason, I've always gravitated away from Go, and always jump on an opportunity to make fun of it (as I have here).

Re: Be careful with Go struct embedding

#25
post #12
post #2

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

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

Unioning interfaces like this does seem convenient for composition/mixin patterns, I'm not sure if extending it to structs in general seems worth the cost of potential footguns though, especially external libraries and such where you probably don't want to think about the full potential tree of embedding conflicts.

Re: Be careful with Go struct embedding

#29
post #2

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

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.

This can be simplified, conflicting field names at the same level also don't compile:

https://go.dev/play/p/D3eFi9_can8

Conflicting functions at nested levels also compile:

https://go.dev/play/p/xXXDZCjQJOh

It's not about method vs field, it's about the nesting level of the conflicting identifier, if it's at the same level there's an error, if it's at different levels, the higher level hides the lower level identifier:

https://go.dev/doc/effective_go#embedding

Re: Be careful with Go struct embedding

#30
post #3

Earlier quoted context omitted.

Because type Foo struct { sync.Mutex whatever string } var foo Foo foo.Lock() foo.whatever = 42 foo.Unlock() is convenient.

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.

Alas, locks don't compose, ie often your users will have to know about the internals when you are using locks.

But it's good advice when it works.

Post reply on HN