I find that people try to use interfaces like they’re using an OO language. Go is not OO.
Loudest arguments against returning concrete types were on the terraform core team and the excuse was it makes testing easier. I disagree.
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.
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.
I assume this is because on is an array of struct pointers and the other is an array of fat pointers, since Go has reified interfaces (unlike higher-level languages).
This is fine for a lot of general purpose code that exits when running into problems. But when errors are an expected part of a long lived process, like an API, it’s painful to build logic around and conditionally handle them.
The ergonomics of errors.Is and As are pretty bad and there doesn’t seem to be a clear indication as when to expect a sentinel, concrete, or pointer to a concrete error.
All that to say, I think Go’s errors really illustrate the benefit of “return values, not interfaces”. Though for errors specifically, I’m not sure you could improve them without some pretty bad tradeoffs around flexibility.
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.
Go's standard library interfaces (like net.Conn) earned their place.
Premature interfaces calcify mistakes and that's what the guideline pushes back on.