I’m not a big fan of contrived examples with imaginary use cases, because they tend to fall short in actual use.
> “Accept interfaces, return structs”
Real example from personal (professional) life against that: Amazon AWS SDK defined S3 as a struct. That makes it bloody hard to mock during unit testing. I eventually ended up defining my own “s3iface” interface with corresponding mock to combat this, but now I have to repeat this for every program that uses it. Ok, I’ll separate it into its own package and include that everywhere. Wait, what was the point of the AWS SDK not doing this directly, again? Oh, look, they eventually ended up doing just that: providing an interface version of their S3 struct:
My real world AWS sdk s3 experience: I have an uploader interface (I only needed to upload). The concrete uploader has the s3 code. The fake uploader has any custom result/err I need to test against.
But this advice is kind of ridiculous in its unqualified form as you have presented it. If folks followed your advice, there wouldn't be an "io" package in the standard library. So why does it exist? Because it's damn useful to not define interfaces every single time you need them. It gets old real fast.
To take your point and run with it: in the face of evolving code, returning an interface from a function is actually more conservative than returning a struct. If you return a concrete type of some sort, then as you evolve your code, you are obligated to continue to return that specific concrete type. As your implementation of the function changes, you might want to return a different concrete type, but you are prevented from doing so. You either have to change the signature, which would break callers, or else you would need to grow that concrete type to handle all the possible ways in which it would want to represent its data.
I realize that this goes against the Go ethos of "don't think about the future; think about the now". Having said that, I don't understand why returning interfaces is seen as premature design while accepting interfaces is seen as reasonable and expected. Surely both can be examples of premature abstraction.
I'm certainly not saying that you should always return interfaces, or that returning interfaces will prevent all future refactoring pain. But working with interfaces instead of concrete types is more likely to allow your API to remain stable even as the implementation evolves.
> That makes it bloody hard to mock during unit testing.
Can you share why? Not trying to say you are wrong, but I am guessing that part of this stems from some limitations in the way interfaces work in Go, and this could make a good user experience report.
> “Accept interfaces, return structs”
Real example from personal (professional) life against that: Amazon AWS SDK defined S3 as a struct. That makes it bloody hard to mock during unit testing. I eventually ended up defining my own “s3iface” interface with corresponding mock to combat this, but now I have to repeat this for every program that uses it. Ok, I’ll separate it into its own package and include that everywhere. Wait, what was the point of the AWS SDK not doing this directly, again? Oh, look, they eventually ended up doing just that: providing an interface version of their S3 struct:
https://godoc.org/github.com/aws/aws-sdk-go/service/s3/s3ifa...
> Package s3iface provides an interface to enable mocking the Amazon Simple Storage Service service client for testing your code.
With the exact same name, to boot.
Please consider providing and returning interfaces. Even if your struct is the only type implementing it.