Hacker News new | ask | show | jobs
by nemo1618 314 days ago
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.

3 comments

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 that using embedding for discriminating unions if a good idea. It would work, but it does not force the user to do the discrimination. I would say that explicit typecasting at the point of discrimination is safer. Without it, nothing prevents you from using one field from one variant of the union, and another from a different variant.

Introduction of proper discriminated unions would be great.

I'm not sure I understand you, or you understand me. I'm saying this is okay:

  type Order struct {
   Type        OrderType
   CommonAttr1 int
   CommonAttr2 string
  }
  
  type OrderTypeA struct {
   Order
   TypeAAttr1 int
   TypeAAttr2 string
  }
  
  type OrderTypeB struct {
   Order
   TypeBAttr1 int
   TypeBAttr2 string
  }
And yes you should convert to OrderTypeA or OrderTypeB at the first opportunity in domain code, and only convert from them at the latest opportunity.

You seem to be under the impression that I'm advocating for something like

  type OrderUnion struct {
   CommonAttr1 int
   CommonAttr2 string
   TypeAAttrs
   TypeBAttrs
  }
That's what I consider going crazy.
> 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.

You said struct embedding could be used for discriminated unions, but there's no mechanism to discriminate between union variants here.
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.)

yup, less than 24 hours after writing that comment, I found myself embedding a struct so that I could override one method in a test, haha
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.