Live data from Hacker News

Go subtleties

harrisoncramer.me

11–20 of 190 posts

Re: Go subtleties

#11
post #9

Ah the old nil values boxed into non-nil interfaces. Even after 8 years writing go code almost every day this still bites me occasionally. I've never seen code that actually uses this. I understand why it is the way it is but I hate it.

Yes, that'a bit too late after ten+ years perhaps but I wished we had a nil type and checking whether the interface is empty was a type assertion. In all other cases, like any(2) == 2, we compare the values. Then again that would mean that the nil identifier would be coerced into a typed nil and we would check for the nilness of what is inside an interface in any(somepointer) == nil. wrt the current behavior, it also…

Agree the ship has likely sailed, but if it could be addressed wouldn't it be nice to remove nil value interfaces altogether? Maybe start by letting new interface types declare/annotate that they don't box nil values? Then one day that becomes the default. Oh well.

Re: Go subtleties

#12
post #6
post #5

Great list of why one can love and hate Go. I really did enjoy writing it but you never get the sense that you can be truly certain your code is robust because of subtle behaviour around nil.

I guess as a corollary, Go really rewards writing the dumbest code possible. No advanced type shenanigans, no overuse of interfaces, no complex composition of types. Then you will end up with a very fast, resource light system that just runs forever.

To be fair, checking if an interface is nil is very dumb code, and the fact that it doesn't work is one of my biggest gripes with the language. In this case it's clearly the language (creators) who's dumb

Re: Go subtleties

#13

  The time.After function creates a channel that will be sent a message after x seconds.
Or, will it? https://github.com/golang/go/issues/24595

  ... even though the value is nil, the type of the variable is a non-nil interface... Go "boxes" that value in an interface, which is not nil. This can really bite you if you return interfaces from functions
Bit me when I was noob. These days, I fail build if ireturn fails.

go install github.com/butuzov/ireturn/cmd/ireturn@latest ireturn ./...

  Go 1.25 introduced a waitgroup.Go function that lets you add Go routines to a waitgroup more easily.
sync.WaitGroup(n) panics if Add(x) is called after a Done(-1) & n isn't now zero. Unsure if WaitGroups and easy belong in the same sentence. May be they do, but I'd rather reimplement Java's CountDownLatch & CyclicBarrier APIs in Go instead.

  When you embed structs, you also implicitly promote any methods they contain ... Say, for instance, you embed a time.Time struct onto a JSON response field and try to marshal that parent ... Since the time.Time method has a MarshalJSON() method, the compiler will run that over the regular marshalling behavior
#£@&+!

Re: Go subtleties

#14
post #10

Ah the old nil values boxed into non-nil interfaces. Even after 8 years writing go code almost every day this still bites me occasionally. I've never seen code that actually uses this. I understand why it is the way it is but I hate it.

The advice I've read (and follow) is always to return values, not interfaces, from functions and test for nil against them. That IME tends to nip the majority of nil interface problems in the bud.

Exactly.

I find that people try to use interfaces like they’re using an OO language. Go is not OO.

Re: Go subtleties

#15
post #4

Ah the old nil values boxed into non-nil interfaces. Even after 8 years writing go code almost every day this still bites me occasionally. I've never seen code that actually uses this. I understand why it is the way it is but I hate it.

I ditched Go after an evaluation years ago. I can remember it was an issue with nil pointers being non-intuitive that turned me off. And exception handling. A pity because the runtime and ecosystem/community seemed pretty good.

> And exception handling

If you read & write Go regularly, the rather verbose error handling simply fades into the background.

That said, errors in Go don't really translate to Exceptions as generally thought of; panic, however; may be does.

Making changes to error handling wasn't for the lack of trying, though: https://news.ycombinator.com/item?id=44171677

> issue with nil pointers

This is why most APIs strive for a non-nil zero value, where possible, as methods (on structs) can still dictate if it will act on a pointer. Though, I get what you're saying with Go missing Optional / Maybe / ? operator, as the only other way to warn about nil types is through documentation; ex: https://github.com/tailscale/tailscale/blob/afaa23c3b4/syncs... (a recent example I stumbled upon).

Static code analysers like nilaway (https://news.ycombinator.com/item?id=38300425) help, but these aren't without false positives (annoying) & false negatives (fatal).

Re: Go subtleties

#16
post #10

Ah the old nil values boxed into non-nil interfaces. Even after 8 years writing go code almost every day this still bites me occasionally. I've never seen code that actually uses this. I understand why it is the way it is but I hate it.

The advice I've read (and follow) is always to return values, not interfaces, from functions and test for nil against them. That IME tends to nip the majority of nil interface problems in the bud.

Return concrete types, accept interfaces. Returning interfaces hides behavior and hampers evolution; accept interfaces so callers can swap implementations. For testing, mock the dependency, not the return value.

Loudest arguments against returning concrete types were on the terraform core team and the excuse was it makes testing easier. I disagree.

Re: Go subtleties

#18
post #4

Earlier quoted context omitted.

I ditched Go after an evaluation years ago. I can remember it was an issue with nil pointers being non-intuitive that turned me off. And exception handling. A pity because the runtime and ecosystem/community seemed pretty good.

It's fantastic concise language and standard library steered by people who are determined to keep it simple and intuitive... which IMO makes it all the more odd that it has this obvious foot gun trap where `!= nil` doesn't always mean what you might think.

The “simplicity” of Go is just virtue signaling. It has gotchas like that all over the language, because it’s not actually simple.

Re: Go subtleties

#19

As somebody who only views Go from a distance, I see this list as a combination of „what‘s the big deal?“ and „please don‘t“.

I'm amused by posts like this because it shows that Go is finally slowly moving away from being an unergonomically simplistic language (its original USP?) to adopt features a modern language should have had all along.

My experience developing in it always gave me the impression that the designers of the language looked at C and thought "all this is missing is garbage collection and then we'll have the perfect language".

I feel like a large amount of the feeling of productivity developers get from writing Go code originates from their sheer LOC output due to having to reproduce what other languages can do in just a few lines thanks to proper language & standard library features.

Re: Go subtleties

#20
post #10

Earlier quoted context omitted.

The advice I've read (and follow) is always to return values, not interfaces, from functions and test for nil against them. That IME tends to nip the majority of nil interface problems in the bud.

Return concrete types, accept interfaces. Returning interfaces hides behavior and hampers evolution; accept interfaces so callers can swap implementations. For testing, mock the dependency, not the return value. Loudest arguments against returning concrete types were on the terraform core team and the excuse was it makes testing easier. I disagree.

This advice of "returning concrete types" is in most cases a horrible anti-pattern that prevents evolution due to lack of information hiding. It has been also deliberately broken in the standard library in several places. This "advice" cannot be generically applied. Places where it is has been deliberately broken:

   net.Dial (Conn, error) 
   image.Decode(r io.Reader) (Image, string, error)
   sha256.NewXXX() hash.Hash
   flate.NewReader(r io.Reader) io.ReadCloser
   http.NewFileTransport(fs FileSystem) RoundTripper
Regarding `os.File`, the Go team even said: “If we were starting from scratch, we might do it differently.”

That’s why Go added abstractions later like fs.FS and fs.File.

   embed/fs.Open again deliberately breaks this.
Whereas consider its counterpart net.Conn. net.Conn is one of the most successful interfaces in the Go standard library. It’s the foundation of the net, net/http, tls, and net/rpc packages, and has been stable since Go 1.0. It didn't need a replacement fs.Fs.

If you will always only ever have one implementation in absolute permanence and no mocking/fake/alternative implementation is ever required in eternity, return a concrete type. Otherwise, consider whether returning an interface makes more sense.

Post reply on HN