Interfaces

Go · Practical · 14 min

Interfaces are Go's main tool for abstraction, testing seams and plugging into the standard library.

An interface type lists method signatures. Any type that has those methods satisfies it implicitly — there's no implements keyword:

type Shape interface { Area() float64 }

Every type with an Area() float64 method is a Shape, including types written before the interface existed. This lets consumers define the small interface they need. The idiom is "accept interfaces, return concrete types", and to keep interfaces small: the standard library's io.Reader (Read(p []byte) (int, error)), io.Writer and fmt.Stringer (String() string) each have one method, which is why so many types compose with them.

An interface value holds a (dynamic type, dynamic value) pair. any (alias for interface{}) has no methods, so it can hold anything — use it sparingly, since you lose static checking.

To get the concrete value back:

  • Type assertion: s, ok := v.(fmt.Stringer). Without ok, a failed assertion panics.
  • Type switch: switch x := v.(type) { case int: ...; case string: ... } — x has the case's type in each branch.

A frequent gotcha: an interface is nil only if both type and value are nil. Returning a nil *MyErr as an error produces a non-nil interface. Return a literal nil instead.

To check at compile time that a type implements an interface, write var _ Shape = (*Square)(nil).

Example

package main

import (
	"fmt"
	"math"
)

type Shape interface {
	Area() float64
}

type Circle struct{ R float64 }
type Square struct{ S float64 }

func (c Circle) Area() float64  { return math.Pi * c.R * c.R }
func (s Square) Area() float64  { return s.S * s.S }
func (s Square) String() string { return fmt.Sprintf("square(%g)", s.S) }

func describe(v any) string {
	switch x := v.(type) {
	case fmt.Stringer:
		return x.String()
	case Shape:
		return fmt.Sprintf("shape with area %.1f", x.Area())
	default:
		return "unknown"
	}
}

func main() {
	for _, s := range []Shape{Circle{1}, Square{2}} {
		fmt.Println(describe(s)) // shape with area 3.1, then square(2)
	}
}

Remember

  • Types satisfy interfaces implicitly by having the methods
  • Keep interfaces small and define them where they are used
  • Use comma-ok assertions or type switches to recover concrete types

Watch out for

  • Returning a typed nil pointer as an interface — the interface is not nil.

Check yourself

  1. Which types satisfy `interface{ Close() error }`?
  2. What does `n := v.(int)` do if v holds a string?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: go.dev/ref/spec#Interface_types