How it works
The explanation, with examples you can run.
Implicit satisfaction
type Store interface { Get(id string) (Item, error)}*PostgresStore, *MemoryStore and a hand-written test fake all satisfy Store as long as they have that method. The package that uses the interface can declare it with only the methods it calls, and the implementing packages never import it. The standard library works this way: io.Reader has one method, and files, network connections, buffers and gzip streams all satisfy it.
Keep interfaces small. "Accept interfaces, return concrete types" is the usual advice: return *PostgresStore from the constructor and let callers decide which interface they want.
The nil interface trap
package main import ( "errors" "fmt") type NotFound struct{ Key string } func (e *NotFound) Error() string { return e.Key + " not found" } func find(key string) error { var err *NotFound // nil pointer if key == "missing" { err = &NotFound{Key: key} } return err // always a non-nil interface: type *NotFound, value maybe nil} func findFixed(key string) error { if key == "missing" { return &NotFound{Key: key} } return nil} func main() { fmt.Println(find("here") == nil) // false fmt.Println(findFixed("here") == nil) // true var nf *NotFound fmt.Println(errors.As(find("missing"), &nf), nf.Key)}find("here") returns the pair (*NotFound, nil). Its type is set, so == nil is false and every caller thinks something failed. The fix is to never route a success through a typed pointer variable: return the literal nil. The same trap catches any interface, not only error.
Method sets
A method with a value receiver, func (t T) M(), can be called on a T or a *T. A method with a pointer receiver, func (t *T) M(), belongs only to *T's method set. So if any method an interface needs has a pointer receiver, you must store a pointer in the interface. A compile-time check is a common idiom:
var _ Store = (*MemoryStore)(nil) // fails to compile if *MemoryStore lacks a methodType assertions and switches
v, ok := x.(Concrete) gets the concrete value back; without ok it panics on a mismatch. A type switch (switch v := x.(type)) tests several types. Use them sparingly: code that keeps asking "what are you really?" usually wants another method on the interface. For errors, use errors.As, which also looks inside wrapped errors.
any is an alias for interface{}, the interface with no methods, which every type satisfies. Storing a value in it can make the value escape to the heap, so hot paths often avoid it; generics are now the usual way to write code that works for many types without losing static types.
Check yourself
3 questions. Get them all right to finish this topic.
0 of 3 right so far. Get all three to finish this topic.