Hacker News new | ask | show | jobs
by Manishearth 3511 days ago
A lot of this comment resonates with my experience and views :)

> You'll note that i didn't list Generics. I know that's high on peoples list, but not mine

This is me too.

Been programming in Rust for 3 years, and picked up Go two years ago. I like the language; I like how it feels like "C but safety net". I haven't used it for anything important (course projects a bit), but this is because so far Rust works for almost all my needs and for everything else I use Python. But I'd be happy to use Go if someone asked me to or there was a reason why Python wasn't suitable.

However, generics aren't what I want in Go. I get that a language without generics can get complex and perhaps slow to compile and has other issues too. On the other hand, enums are on the top of the list for me. Especially for the message-passing programs I tend to write in Go. There have been times when I've hacked together an enum system using interfaces and hidden methods but its not great. I've grown to get used to error handling, but I do think it can be improved a lot. Package management was a major gripe of mine but it seems like y'all are fixing that :D

I have not written production software in Go so panic-proof channels haven't been on my radar but yeah, that one makes sense.

> didn't want to make a choice that would cause them to spend weeks/months feeling unproductive.

+1 I have often recommended Go to people who don't have time but want to learn something new. If you want to actually spend time writing software from scratch in week 1 of learning the language, Go is amazing. I recommend Rust often too, but I usually find out how much time they have and/or their background first.

> Rust gives me a peace of mind that Go never came close to.

Again, big +1. For me it is two effects -- one is that Rust feels safer, and the other is that as a Rustacean Go feels wasteful at times. After programming in Rust for that long, losing performance at any corner for no reason irks me. In Rust, for example, most folks will avoid reference counting and trait objects and heap allocation as much as possible. If you see an unnecessary trait object it _feels wrong_. This is a perfectly sensible attitude to have in Rust. For me, it often carries on into Go. But Go loves interfaces and has garbage collection (with good escape analysis, but a GC nevertheless). Every time I use an interface object in Go, it _feels wrong_. It shouldn't. And I've learned to ignore it -- if I'm writing an application in Go; perf probably didn't matter enough for this to matter. (In fact, the odd unnecessary trait object in a typical Rust lib is usually no big deal either.). But, that nagging feeling is still there :)

1 comments

You can also build enums by building a struct with a `Tag` field. This works well and generally has better performance characteristics than using interfaces.
That approach has its own problems, such as wasting memory (need to store any data for each tag value separately, rather than benefiting from overlapping storage ala Rust enums or C tagged unions), as well as losing type safety: one has to manually remember which (groups of) fields correspond to which tag values (although the Go loss of type safety is far better/more controlled than the one for C tagged unions). Using interfaces has neither of these problems.
Note that using interfaces doesn't get you all the type safety either, since there's no exhaustive matching. But it's good enough (aside from the possible perf issues) for most use cases.
> such as wasting memory

Granted, but I'll happily trade a few bytes of stack for zero allocs in many cases.

> rather than benefiting from overlapping storage ala Rust enums or C tagged unions

Of course, but we don't have those in Go, do we?

> as well as losing type safety

Tagged structs are not meaningfully less type safe than interfaces.

> one has to manually remember which (groups of) fields correspond to which tag values

Use an enum for the tag: https://play.golang.org/p/LI2Bh231W3

> Tagged structs are not meaningfully less type safe than interfaces.

Of course they are. What if you read from or write to the wrong field?

> Use an enum for the tag

Your example has one contained type per variant. In Rust it is common to have an enum like

    enum Foo {
      Variant1(String, u8, Vec<u8>),
      Variant2(u8),
      Variant3(String),
      Variant4(f32),
      Variant6(f32)
      Variant5(bool, bool, String)
   }

Naming tags for something like this would be hard. Some of the fields would be shared between variants. Some would not.
> Of course they are. What if you read from or write to the wrong field?

This falls under the rubric of "not meaningfully less type safe". This data structure is central in a large project of mine, and I have maybe 3 places where I switch on the type flag. I'm not proposing this as a general purpose replacement for interfaces, only a useful way to abstract over a few known types when you can't afford all of the allocs.

> Your example has one contained type per variant. In Rust it is common to have an enum like enum Foo { Variant1(String, u8, Vec<u8>), Variant2(u8), Variant3(String), Variant4(f32), Variant6(f32) Variant5(bool, bool, String) }

I agree. I don't propose this as a general purpose replacement for Rust's enums.

> I agree. I don't propose this as a general purpose replacement for Rust's enums.

Yeah, that's the thing, Rust enums used this way are very powerful. I'm fine with making tagged structy things in cases like the one mentioned, I feel Go can handle that. I'm missing out on all the useful stuff I can do with proper algebraic datatypes.

That only works well if each "variant" holds the same kind of thing. If not, you have to store them one after the other (space wastage), or use interfaces to store them in the same place (tag isn't necessary anymore, extra boxing).

Rust enums (ADTs) aren't like Java enums where each variant contains the same kind of data.

I understand. I don't mind wasting a few bytes of stack for zero allocs.