Interfaces
Go’s main abstraction, satisfied without ever being declared.
- Write an interface and two types that satisfy it without declaring it.
- Explain why an interface holding a nil pointer is not
nil. - Fix a function that returns a typed nil pointer as an
error.
How does a type implement an interface in Go?
Automatically: there is no "implements" keyword. An interface lists method names. Any type with those methods satisfies it automatically. You never declare the relationship. The type does not need to know the interface exists, and the interface can be written years later by someone else.
That sounds like a small detail. It is the reason io.Reader works with files, network connections, buffers, gzip streams, and HTTP bodies, none of which were written with each other in mind.
type Shape interface { Area() float64}type Circle struct{ R float64 }func (c Circle) Area() float64 { return math.Pi * c.R * c.R }type Rect struct{ W, H float64 }func (r Rect) Area() float64 { return r.W * r.H }// Both satisfy Shape. Neither says so.shapes := []Shape{Circle{2}, Rect{3, 4}}for _, s := range shapes { fmt.Println(s.Area())}Why are small interfaces better?
A small interface is easy to satisfy, so many types can plug into it. "The bigger the interface, the weaker the abstraction," as the Go Proverbs put it. The most widely used interfaces in the standard library have one method. io.Reader has Read. error has Error. http.Handler has ServeHTTP. A one-method interface is easy to satisfy, so everything plugs into it.
- Accept interfaces, return structs. Take the narrowest thing you need as a parameter; return the concrete type so callers keep every method.
- Do not create an interface until you have a second implementation or a test that needs a fake. A one-implementation interface is indirection with no payoff: every reader has to jump through it to find the real code.
Why is an interface holding a nil pointer not nil?
Because an interface is nil only when both its type and its value are empty, and a nil pointer still has a type. This is one of the best-known Go bugs, and the Go FAQ has an entry for it. Meet it now rather than in production. An interface value holds two things: a type, and a value. It is nil only when both are empty.
type MyError struct{}func (e *MyError) Error() string { return "boom" }func doWork() error { var e *MyError = nil // a nil POINTER return e // wrapped into an interface: type=*MyError, value=nil}if err := doWork(); err != nil { fmt.Println("this DOES print") // err is not nil! The type half is set.}func doWork() error { if somethingWrong { return &MyError{} } return nil // correct — a true nil interface}Try it yourself
package mainimport "fmt"type Shape interface{ Area() float64 }type Rect struct{ W, H float64 }func (r Rect) Area() float64 { return r.W * r.H }type MyError struct{ Field string }func (e *MyError) Error() string { return e.Field + " is invalid" }func broken() error { var e *MyError = nil return e // a nil POINTER inside a non-nil interface}func main() { var s Shape = Rect{3, 4} // no "implements" needed fmt.Println("area:", s.Area()) if err := broken(); err != nil { fmt.Println("the nil trap: err != nil is TRUE") // surprising, and correct } var err error = &MyError{Field: "email"} // *MyError has Error(), so it is an error fmt.Println("as an error:", err)}- Interfaces are satisfied implicitly. Any type with the methods qualifies.
- The smaller the interface, the more useful. One method is the ideal.
- Accept interfaces, return structs, and define the interface where it is used.
- An interface holding a nil pointer is not nil. Return the literal
nil.
Shape interface with two implementations and a function taking []Shape. Then reproduce the nil-interface trap and fix it.Rect already satisfies Shape. Make Square satisfy it too by writing its Area method (side times side), then write TotalArea(shapes []Shape) float64, which adds up the area of every shape. Example: TotalArea([]Shape{Rect{W: 2, H: 3}, Square{Side: 2}}) → 10.Tab indents. Press Esc, then Tab, to leave the editor.
- 1Add
var _ Shape = (*Circle)(nil)then delete a method and read the compile error. - 2Return a nil
*MyErroras anerrorand checkerr != nil. - 3Write a function taking
io.Writerand call it with a file, a buffer andos.Stdout.