Interfaces
Behaviour described as a set of methods, satisfied without ever saying so, and the small interfaces that keep Go code easy to swap and easy to test.
An interface is a list of methods
An interface says what a value can do, not what it is: type Shape interface { Area() float64 }. Anything with an Area() float64 method is a Shape — a circle, a square, a type you write next week. A function that takes a Shape works with all of them, and knows nothing else about them.
There is no implements keyword. Satisfaction is implicit: the method set either matches or it does not, checked by the compiler at the call. That is why interfaces can be defined next to the code that uses them, describing exactly the behaviour needed and nothing more.
Small interfaces are strong interfaces
The standard library's most useful interfaces have one method: io.Writer is Write([]byte) (int, error), fmt.Stringer is String() string. One method means almost anything can satisfy it, which means a function that takes one can accept almost anything — the opposite of a rigid type.
The empty interface — written any — is satisfied by every value at all, so it tells the compiler nothing. It belongs at the edges of a program: decoding JSON you have not seen, logging a value whose shape you do not know. Inside a program, the method set is the information you actually want written down.
Satisfaction can be accidental
Satisfaction is checked where the value is used, and that has a useful consequence: a type can satisfy an interface it has never heard of. fmt.Stringer is one method, so any type with String() string prints through %v — including a type written years before the interface existed. The same openness is a hazard. Add a method to a type and it may start satisfying an interface somewhere else, changing behaviour you did not touch. That is why small interfaces are safer: one method is a small promise, and a type is less likely to keep it by accident.
Looking at what is inside
When a value of interface type must be checked, there are two forms. A type assertion tests one type: c, ok := shape.(Circle). A type switch walks several: switch v := shape.(type) { case Circle: … }, binding the concrete value in each branch, which is the tidy replacement for a chain of assertions.
Reach for them sparingly. If the program needs a type switch in the middle of ordinary work, the interface is usually too vague — or the behaviour belongs as another method on the interface itself.
A worked method set: if Area has a value receiver, both Circle and *Circle satisfy Shape. If Area has a pointer receiver, only *Circle does, because the method set of Circle contains value-receiver methods alone. The compiler reports "missing method Area" on the value, which looks wrong until you check the receiver kind on the declaration.
Two shapes, one interface
package main
import "fmt"
type Shape interface {
Area() float64
}
type Circle struct{ R float64 }
type Rect struct{ W, H float64 }
func (c Circle) Area() float64 { return 3.14159 * c.R * c.R }
func (r Rect) Area() float64 { return r.W * r.H }
func total(shapes []Shape) float64 {
sum := 0.0
for _, shape := range shapes {
sum += shape.Area() // the only thing we know: it has Area
}
return sum
}
func main() {
shapes := []Shape{Circle{R: 2}, Rect{W: 3, H: 4}}
fmt.Printf("total area: %.2f\n", total(shapes))
// A type switch, when the program needs the specifics.
for _, shape := range shapes {
switch v := shape.(type) {
case Circle:
fmt.Printf("circle with radius %.0f\n", v.R)
case Rect:
fmt.Printf("rectangle %.0f by %.0f\n", v.W, v.H)
}
}
}One loop, many shapes
Run it, then add a Triangle type with its own Area, put one in the slice, and add a case for it to the type switch.
A method set of one or two is the sweet spot. If an interface grows a fifth method, the code that uses it probably wanted two smaller ones — or a struct.
What an interface value holds
An interface value is a pair: the dynamic type of the value stored in it, and a pointer to that value. Assigning a Circle to a Shape records Circle and the circle; calling Area looks the method up through that type and runs it.
Nil is the interesting part. An interface is nil only when both words are nil. A nil *Circle stored in a Shape still has a type — *Circle — and a nil value, so the interface is not nil, and calling a value-receiver method through it panics before the method body runs.
The method set follows the receiver kind. A value of type T carries only methods declared with a value receiver; *T carries both. That is why one pointer-receiver method changes which of T and *T satisfies an interface — and why the compiler error names a method that looks present in the source.
Three interface mistakes: storing a typed nil in an interface and testing shape == nil (it is not nil); comparing interface values whose dynamic type is a slice or map (that panics, because the values are not comparable); and defining the interface in the package that implements it, rather than the package that uses it. The last one is legal and quietly makes every caller depend on the implementation.
Try these next
- Give one shape a
String() stringmethod and print it with%v. - Store a nil
*Rectin a Shape, testshape == nil, then call Area to see the panic. - Write a function that takes a one-method interface and call it with three different types.
Three ways to pay, one checkout
Run it, then add a `Bank` payment that declines anything over 500 cents. Your checkout function should not need a single change.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
How does a type declare that it satisfies an interface?
