←back to thread

Go is still not good

(blog.habets.se)
644 points ustad | 1 comments | | HN request time: 0.213s | source
Show context
blixt ◴[] No.44983245[source]
I've been using Go more or less in every full-time job I've had since pre-1.0. It's simple for people on the team to pick up the basics, it generally chugs along (I'm rarely worried about updating to latest version of Go), it has most useful things built in, it compiles fast. Concurrency is tricky but if you spend some time with it, it's nice to express data flow in Go. The type system is most of the time very convenient, if sometimes a bit verbose. Just all-around a trusty tool in the belt.

But I can't help but agree with a lot of points in this article. Go was designed by some old-school folks that maybe stuck a bit too hard to their principles, losing sight of the practical conveniences. That said, it's a _feeling_ I have, and maybe Go would be much worse if it had solved all these quirks. To be fair, I see more leniency in fixing quirks in the last few years, like at some point I didn't think we'd ever see generics, or custom iterators, etc.

The points about RAM and portability seem mostly like personal grievances though. If it was better, that would be nice, of course. But the GC in Go is very unlikely to cause issues in most programs even at very large scale, and it's not that hard to debug. And Go runs on most platforms anyone could ever wish to ship their software on.

But yeah the whole error / nil situation still bothers me. I find myself wishing for Result[Ok, Err] and Optional[T] quite often.

replies(18): >>44983384 #>>44983427 #>>44983465 #>>44983479 #>>44983531 #>>44983616 #>>44983802 #>>44983872 #>>44984433 #>>44985251 #>>44985721 #>>44985839 #>>44986166 #>>44987302 #>>44987396 #>>45002271 #>>45002492 #>>45018751 #
Mawr ◴[] No.44985839[source]
> I find myself wishing for Optional[T] quite often.

Well, so long as you don't care about compatibility with the broad ecosystem, you can write a perfectly fine Optional yourself:

    type Optional[Value any] struct {
     value  Value
     exists bool
    }

    // New empty.
    func New[Value any]() Optional[Value] {}

    // New of value.
    func Of[Value any](value Value) Optional[Value] {}

    // New of pointer.
    func OfPointer[Value any](value *Value) Optional[Value] {}

    // Only general way to get the value.
    func (o Optional[Value]) Get() (Value, bool) {}

    // Get value or panic.
    func (o Optional[Value]) MustGet() Value {}

    // Get value or default.
    func (o Optional[Value]) GetOrElse(defaultValue Value) Value {}

    // JSON support.
    func (o Optional[Value]) MarshalJSON() ([]byte, error) {}
    func (o *Optional[Value]) UnmarshalJSON(data []byte) error {}

    // DB support.
    func (o *Optional[Value]) Scan(value any) error {}
    func (o Optional[Value]) Value() (driver.Value, error) {}

But you probably do care about compatibility with everyone else, so... yeah it really sucks that the Go way of dealing with optionality is slinging pointers around.
replies(2): >>44985874 #>>44986959 #
kbolino ◴[] No.44986959[source]
There's some other issues, too.

For JSON, you can't encode Optional[T] as nothing at all. It has to encode to something, which usually means null. But when you decode, the absence of the field means UnmarshalJSON doesn't get called at all. This typically results in the default value, which of course you would then re-encode as null. So if you round-trip your JSON, you get a materially different output than input (this matters for some other languages/libraries). Maybe the new encoding/json/v2 library fixes this, I haven't looked yet.

Also, I would usually want Optional[T]{value:nil,exists:true} to be impossible regardless of T. But Go's type system is too limited to express this restriction, or even to express a way for a function to enforce this restriction, without resorting to reflection, and reflection has a type erasure problem making it hard to get right even then! So you'd have to write a bunch of different constructors: one for all primitive types and strings; one each for pointers, maps, and slices; three for channels (chan T, <-chan T, chan<- T); and finally one for interfaces, which has to use reflection.

replies(1): >>44990133 #
Mawr ◴[] No.44990133[source]
For JSON I just marshall to/from:

    {
     "value": "value",
     "exists: true,
    }
For nil, that's interesting. I've never ran into issues there, so I never considered it.
replies(1): >>44990750 #
1. kbolino ◴[] No.44990750[source]
Ideally, I would want Optional[T] to encode the same as T when a value is present, and to encode in a configurable way when the value is absent. Admittedly, the nothing to null problem exists with *T too, and even with *T and `json:",omitempty"`, you get the opposite problem (null turns to nothing). I didn't think about that at the time, so it's really more of an issue with encoding/json rather than Optional[T] per se. However, you can't implement MarshalJSON and output nothing as far as I know.