Interfaces
Prerequisites: Pointer receivers & method sets
An interface is a set of method signatures. Any type that has those
methods satisfies the interface automatically — there is no implements
keyword. This lets you write functions that accept "anything that can do X".
Go code favours small interfaces, often with a single method, such as
fmt.Stringer (String() string), io.Reader (Read([]byte) (int, error))
and error (Error() string).
Syntax
type Shape interface {
Area() float64
}
var s Shape = Rect{W: 3, H: 4} // Rect satisfies Shape because it has Area()
r := s.(Rect) // type assertion: panics if s does not hold a Rect
r, ok := s.(Rect) // "comma ok" form: ok is false instead of panicking
switch v := s.(type) { // type switch
case Rect:
// v is a Rect here
case Circle:
// v is a Circle here
}
The empty interface interface{} — spelled any since Go 1.18 — has no
methods, so every type satisfies it. Use it sparingly: you lose compile-time
type checking and have to assert the type back out.
Example
package main
import (
"fmt"
"math"
)
type Shape interface {
Area() float64
}
type Rect struct{ W, H float64 }
type Circle struct{ R float64 }
func (r Rect) Area() float64 { return r.W * r.H }
func (c Circle) Area() float64 { return math.Pi * c.R * c.R }
// String makes Rect a fmt.Stringer, so fmt uses it when printing.
func (r Rect) String() string { return fmt.Sprintf("Rect(%gx%g)", r.W, r.H) }
func describe(v any) string {
switch x := v.(type) {
case int:
return fmt.Sprintf("int %d", x)
case string:
return fmt.Sprintf("string of length %d", len(x))
case Shape:
return fmt.Sprintf("shape with area %.2f", x.Area())
default:
return fmt.Sprintf("something else: %T", x)
}
}
func main() {
shapes := []Shape{Rect{W: 3, H: 4}, Circle{R: 1}}
for _, s := range shapes {
fmt.Printf("%v -> %.2f\n", s, s.Area())
}
var s Shape = Circle{R: 2}
if c, ok := s.(Circle); ok {
fmt.Println("radius:", c.R)
}
if _, ok := s.(Rect); !ok {
fmt.Println("not a Rect")
}
fmt.Println(describe(42))
fmt.Println(describe("gopher"))
fmt.Println(describe(Rect{W: 2, H: 5}))
fmt.Println(describe(3.5))
}
Expected output
Rect(3x4) -> 12.00
{1} -> 3.14
radius: 2
not a Rect
int 42
string of length 6
shape with area 10.00
something else: float64
Circle has no String method, so %v prints its fields as {1}.
Common mistakes
- Defining big interfaces up front "just in case". Define interfaces where they are used, with only the methods that code needs.
- Using the single-value assertion
s.(Rect)when you aren't sure of the type — it panics on a mismatch. Use thev, ok :=form. - Assuming an interface holding a nil pointer is
nil. An interface is onlynilwhen it has no type and no value;var p *Rect; var s Shape = pgivess != nil. - Reaching for
anywhen a concrete type or a small interface would do.
Practice
Define an interface Animal with a method Sound() string. Create Dog
and Cat types that satisfy it, put one of each in a []Animal, and print
each sound.
Show solution
package main
import "fmt"
type Animal interface {
Sound() string
}
type Dog struct{}
type Cat struct{}
func (Dog) Sound() string { return "Woof" }
func (Cat) Sound() string { return "Meow" }
func main() {
animals := []Animal{Dog{}, Cat{}}
for _, a := range animals {
fmt.Println(a.Sound())
}
}