←back to thread

Go subtleties

(harrisoncramer.me)
235 points darccio | 4 comments | | HN request time: 0.449s | source
Show context
rowanseymour ◴[] No.45666258[source]
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.
replies(4): >>45666614 #>>45666792 #>>45666859 #>>45669163 #
kitd ◴[] No.45666859[source]
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.
replies(4): >>45667017 #>>45667105 #>>45667244 #>>45667694 #
leetrout ◴[] No.45667105[source]
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.

replies(1): >>45667214 #
1. lenkite ◴[] No.45667214[source]
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.

replies(2): >>45667420 #>>45667824 #
2. arccy ◴[] No.45667420[source]
On the contrary, in recent proposal reviews, returning interfaces has been discouraged unless you're trying to make a generic interface like fs.FS, or dispatch functions like net.Dial / image.Decoder.

The advice of returning concrete types is paired with defining interfaces when you need them on the consumer side.

It's returning interfaces that prevents good evolution, since the standard library will not add methods to interfaces, it can only document things like: all current standard library implementations additionally satisfy XXX interfaces.

replies(1): >>45667807 #
3. lenkite ◴[] No.45667807[source]
It seems there is a dichotomy in the real implemented world and your hypothetical advice world.

Due to lack of native support of defaults for optional methods , many interfaces in Go are using hacks for optional methods added by evolution.

The Value interface has a `IsBoolFlag()` optional method not part of the interface signature

The other way for evolution is just add sub-interfaces. Like `io.WriterTo` and `io.ReaderFrom` which are effectively just extensions of `io.Writer` and `io.Reader` with `WriteTo` and `ReadFrom` methods - which are checked for in consumers like `io.Copy`.

Anyways, my point was specifically about generic interfaces and alternative implementations, so it appears you agree.

4. leetrout ◴[] No.45667824[source]
Like anything else there are exceptions to the rule. Pointing to the standard library is a weak position because it is consistently inconsistent.

Go's standard library interfaces (like net.Conn) earned their place.

Premature interfaces calcify mistakes and that's what the guideline pushes back on.