Interfaces

3-minute read
Table of Contents

These are named collections of method signatures (function signatures). Once a struct has the same method signatures as an interface, we say that the struct satisfies that interface - it serves as an implementation of the interface. Here is an example of two structs (rect and circle) which both satisfy a geometry interface:

package main

import (
	"fmt"
	"math"
)

type geometry interface {
	area() float64
	perimeter() float64
}

type rect struct {
	length, width float64
}

type circle struct {
	radius float64
}

func (r rect) area() float64 {
	return r.length * r.width
}

func (r rect) perimeter() float64 {
	return 2 * (r.length + r.width)
}

func (c circle) area() float64 {
	return math.Pi * c.radius * c.radius
}

func (c circle) perimeter() float64 {
	return 2 * math.Pi * c.radius
}

func measure(g geometry) {
	fmt.Println(g)
	fmt.Println(g.area())
	fmt.Println(g.perimeter())
}

func detectCircle(g geometry) {
	if c, ok := g.(circle); ok {
		fmt.Println("circle with radius", c.radius)
	}
}

func main() {
	r := rect{length: 3, width: 5}
	c := circle{radius: 4}

	measure(r)
	measure(c)

	detectCircle(r)
	detectCircle(c)
}
interfaces-geometry.go
Copy

The function measure accepts an interface of type geometry which means it can operate on any struct that satisfies the interface - in this case both the rect and circle. We can call the methods in the interface (area() and perimeter())

The detectCircle function uses a type assertion to check if the struct is a circle at runtime:


if c, ok := g.(circle); ok {
    // do something if the type assertion works
}

Golang uses interfaces to achieve structural typing - duck typing at compile time. Duck typing is based on the saying “if it walks like a duck and quacks like a duck then it’s a duck”.

Type switch

We can use a type switch in order to determine the data type of a variable:

package main

import (
	"fmt"
	"math"
)

type geometry interface {
	area() float64
	perimeter() float64
}

type rect struct {
	length, width float64
}

type circle struct {
	radius float64
}

func (r rect) area() float64 {
	return r.length * r.width
}

func (r rect) perimeter() float64 {
	return 2 * (r.length + r.width)
}

func (c circle) area() float64 {
	return math.Pi * c.radius * c.radius
}

func (c circle) perimeter() float64 {
	return 2 * math.Pi * c.radius
}

func detectType(i interface{}) {
	switch t := i.(type) {
	case circle:
		fmt.Println("I am a circle")
	case rect:
		fmt.Println("I am a rect")
	default:
		fmt.Printf("I am not a shape. I am a(n) %T\n", t)
	}
}

func main() {
	r := rect{length: 3, width: 5}
	c := circle{radius: 4}
	age := 15

	detectType(r)
	detectType(c)
	detectType(age)
}
interfaces-type-switch.go
Copy

Here we detect if the shape is a circle or rect or something else. We can even use g geometry instead of i interface{} in the function declaration of detectType in order to bar ourselves from passing anything that doesn’t satisfy the interface geometry into the function.

Support us via BuyMeACoffee