Remix.run Logo
tymscar 4 days ago

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

amiga386 4 days ago | parent | next [-]

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.

dgl 4 days ago | parent | prev | next [-]

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

whatevertrevor 4 days ago | parent | next [-]

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.

mananaysiempre 4 days ago | parent | prev [-]

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

bilbo-b-baggins 4 days ago | parent [-]

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

mananaysiempre 4 days ago | parent | prev | next [-]

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 4 days ago | parent | prev | next [-]

Because

   type Foo struct { 
       sync.Mutex
       whatever string
   }
   var foo Foo
   foo.Lock()
   foo.whatever = 42
   foo.Unlock()
is convenient.
resonious 4 days ago | parent | next [-]

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

sethammons 4 days ago | parent | prev | next [-]

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.

eru 4 days ago | parent [-]

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.

thrill 4 days ago | parent | prev | next [-]

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 4 days ago | parent | prev [-]

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.

eru 4 days ago | parent [-]

I mostly agree.

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

Funny enough, this is only 'modern' in imperative languages. It's been a staple in the ML family since approximately forever. (But hey, I do appreciate progress when we get it!)

mikepurvis 4 days ago | parent | prev | next [-]

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 4 days ago | parent [-]

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.

typ 4 days ago | parent | prev | next [-]

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

bilbo-b-baggins 4 days ago | parent | prev | next [-]

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.

whatevertrevor 4 days ago | parent [-]

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

divan 4 days ago | parent | prev | next [-]

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.

metadat 4 days ago | parent | prev [-]

IMHO it should be a compiler error. This is just so loose... a wheel fell off.

ShroudedNight 4 days ago | parent [-]

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.

catlifeonmars 4 days ago | parent [-]

Eh it’s about the same level of footgun you might see in C99. It’s not great but you’re being hyperbolic if you ask me.

ShroudedNight 4 days ago | parent [-]

I agree that most scenarios are not going to be so perilous. However, GOTO FAIL[1] would fit your C99 foot gun description neatly, with repercussions that would approach the peril of my metaphor within at least an order of magnitude.

https://en.wikipedia.org/wiki/Unreachable_code#goto_fail_bug