Small Go interfaces
I wanted to test a function that wrote a report without letting the test create files. My first instinct, trained by larger object systems, was to invent a report hierarchy. Go let me stop much earlier.
The function only needs one operation:
type writer interface {
Write([]byte) (int, os.Error)
}
func report(w writer, name string) os.Error {
_, err := w.Write([]byte("hello, " + name + "\n"))
return err
}
A file can satisfy that interface. So can a network connection or a little buffer in a test. None of those types has to announce that it implements writer; the method set is enough.
That last detail was my surprise. I am used to a type naming its interfaces at the type declaration. Here the relationship can be discovered where it is needed. The package containing report can define the narrow interface, even when the concrete type belongs to another package and cannot be edited.
The mechanism is structural. An interface describes a set of methods. A value is assignable to it when its type has those methods with the right signatures. The compiler checks the assignment, so this is not hopeful late binding. Calls through the interface still carry dynamic type information, but the agreement itself needs no explicit declaration.
Pointer methods add one wrinkle. If a method has a pointer receiver, a pointer to the type has that method; the plain value does not. That distinction matters when a method modifies state, and it explains several otherwise puzzling assignment errors.
I now prefer interfaces declared by the code that consumes them, and I keep them as small as the operation permits. A one-method interface is not a toy. It is often a precise seam between a useful function and everything it does not need to know.
The caveat is that an interface should represent behaviour, not merely hide every concrete type on principle. Returning an interface too early can discard useful methods and make code harder to follow. If there is only one implementation and no boundary to protect, the concrete type is frequently clearer.
Here, one method bought a test with no temporary file and no elaborate framework. I will take that trade.