Dev Skills

Interfaces

Go’s main abstraction, satisfied without ever being declared.

Topic 17 of 98 · 5 min to read · 45 min to build
After this topic you can
  • 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.

interface.go
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.

niltrap.go
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.
}
The fix: do not return a variable of a concrete error type (here `*MyError`) as an `error`. On success, return the literal `nil`.
niltrap-fixed.go
func doWork() error {
if somethingWrong {
return &MyError{}
}
return nil // correct — a true nil interface
}

Try it yourself

press Run — this executes on the Go Playground
package main
import "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)
}
Implicit satisfaction, the nil-interface trap, and your own type used as an `error`. Checking wrapped errors (`errors.Is` / `errors.As`) comes in **Error values in depth**.
The short version
  • 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.
Remember: Do not create an interface until there is a second implementation or a test that needs a fake.
Build this before moving on
Write a Shape interface with two implementations and a function taking []Shape. Then reproduce the nil-interface trap and fix it.
Coding challenge · checked by tests
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.

Then try these in your own editor
  1. 1Add var _ Shape = (*Circle)(nil) then delete a method and read the compile error.
  2. 2Return a nil *MyError as an error and check err != nil.
  3. 3Write a function taking io.Writer and call it with a file, a buffer and os.Stdout.
Check yourselfTake the Core language test