Interfaces
fmt mathInterfaces are named collections of method signatures. A type implements an interface simply by having its methods: there is no implements keyword.
package main
import (
"fmt"
"math"
)Here's a basic interface for geometric shapes.
type geometry interface {
area() float64
perim() float64
}We'll implement this interface on rect and circle types.
type rect struct {
width, height float64
}
type circle struct {
radius float64
}To implement an interface in Go, we just need to implement all of its methods.
func (r rect) area() float64 {
return r.width * r.height
}
func (r rect) perim() float64 {
return 2*r.width + 2*r.height
}The implementation for circles.
func (c circle) area() float64 {
return math.Pi * c.radius * c.radius
}
func (c circle) perim() float64 {
return 2 * math.Pi * c.radius
}scale has a pointer receiver, so it can change the circle it is called on.
func (c *circle) scale(k float64) {
c.radius *= k
}scaler is implemented only by types whose method set includes scale.
type scaler interface {
scale(float64)
}A variable with an interface type can call the methods in the interface. Here's a generic measure function taking advantage of this to work on any geometry.
func measure(g geometry) {
fmt.Println(g)
fmt.Println(g.area())
fmt.Println(g.perim())
}Sometimes it's useful to know the runtime type of an interface value. A type assertion does that; the comma-ok form reports whether it worked.
func detectCircle(g geometry) {
if c, ok := g.(circle); ok {
fmt.Println("circle with radius", c.radius)
}
}This line fails to compile if rect ever stops implementing geometry. Value methods are in the method set of both rect and *rect.
var _ geometry = rect{}A pointer method is only in the method set of the pointer. *circle implements scaler. A circle value does not, even though a call on a variable still works.
var _ scaler = (*circle)(nil)
// var _ scaler = circle{}
// error: cannot use circle{} (value of struct type circle) as scaler value in variable declaration: circle does not implement scaler (method scale has pointer receiver)
func main() {
r := rect{width: 3, height: 4}
c := circle{radius: 5}The circle and rect types both implement the geometry interface, so we can use instances of them as arguments to measure.
measure(r)
measure(c)
detectCircle(r)
detectCircle(c)A slice of an interface type can hold values of different types.
shapes := []geometry{r, c, rect{1, 1}}
total := 0.0
for _, s := range shapes {
total += s.area()
}
fmt.Printf("total area %.2f\n", total)The empty interface, any, is satisfied by every type.
var anything any = 42
fmt.Println(anything)
anything = "now a string"
fmt.Println(anything)Calling a pointer method on a variable is fine: Go takes its address. That does not put the method in the value's method set, which is why circle itself is not a scaler above.
grown := circle{radius: 5}
grown.scale(2)
fmt.Println("scaled radius:", grown.radius)An interface is nil only when it holds neither a type nor a value.
var noShape geometry
fmt.Println("nil interface:", noShape, noShape == nil)A nil pointer stored in an interface is not a nil interface. The type is *circle, so the comparison is false, and calling a method panics.
var hole *circle
var asShape geometry = hole
fmt.Println("typed nil:", asShape, asShape == nil)
func() {
defer func() { fmt.Println("nil call panicked:", recover() != nil) }()
asShape.area()
}()
}$ go run ./21_interfaces {3 4} 12 14 {5} 78.53981633974483 31.41592653589793 circle with radius 5 total area 91.54 42 now a string scaled radius: 10 nil interface: <nil> true typed nil: <nil> false nil call panicked: true