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)
}
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)
}
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.