As it has already been mentioned, returning an interface should be considered something exceptional.
Returning errors of type error
which is an interface is one of those exception.
Returning an interface that represents an unexported type is the other exception. But why would you have an exported interface that describes an unexported struct instead of just having an exported struct?
The reason is simple, that allows you a higher degree of control on how that struct is constructed.
Compare this two pieces of code:
type MyType struct { MyField string}func NewMyType(value string) MyType { return MyType{value}}func (t MyType) MyMethod() string { return t.MyField}
type MyType interface { MyMethod() string}type myType struct { MyField string}func NewMyType(value string) MyType { return myType{value}}func (t myType) MyMethod() string { return t.MyField}
In the first case I would be able to do: myVar := MyType{}
while in the second case I won't be able to do so, I am forced to use the provided constructor. The first case also allows to modify the field value after creation which is not allowed in the second case. Making the field unexported will solve the second part but not the first.
This example is obviously trivial, but being able to construct invalid structs may have a horrible impact. By having specific constructors you can ensure that the object is in a valid starting state and you will only need to make sure it always stays in a valid state. If you can't ensure that, you may need to check that it is in a valid state at the start of every method.
For example consider a DB request. It needs a DB connection. If the user is able to create a DB request without a DB connection you will have to check that it is valid in every method. If you enforce him to use a constructor you can check at creation time and done.