Go Interface

An interface is a type defined using a set of method signatures. It defines the behavior for similar type of objects.

Here is an interface that defines the behavior for Geometrical shapes:

// Go Interface - `Shape`
type Shape interface {
	Area() float64
	Perimeter() float64
}

The interface is defined using the type keyword, followed by a name and the keyword interface. Then we specify a set of method signatures inside curly braces.

Implementing an interface in Go

To implement an interface, you just need to implement all the methods declared in the interface.

Go Interfaces are implemented implicitly

Unlike other languages like Java, you don’t need to explicitly specify that a type implements an interface using something like an implements keyword.

Here are two Struct types that implement the Shape interface:

// Struct type `Rectangle` - implements the `Shape` interface by implementing all its methods.
type Rectangle struct {
	Length, Width float64
}

func (r Rectangle) Area() float64 {
	return r.Length * r.Width
}

func (r Rectangle) Perimeter() float64 {
	return 2 * (r.Length + r.Width)
}
// Struct type `Circle` - implements the `Shape` interface by implementing all its methods.
type Circle struct {
	Radius float64
}

func (c Circle) Area() float64 {
	return math.Pi * c.Radius * c.Radius
}

func (c Circle) Perimeter() float64 {
	return 2 * math.Pi * c.Radius
}

func (c Circle) Diameter() float64 {
	return 2 * c.Radius
}

Using an interface type with concrete values

An interface in itself is not that useful unless we use it with a concrete type that implements all its methods.

Let’s see how an interface can be used with concrete values.

  • An interface type can hold any value that implements all its methods

    package main
    
    import (
        "fmt"
        "math"
    )
    
    func main() {
        var s Shape = Circle{5.0}
        fmt.Printf("Shape Type = %T, Shape Value = %v\n", s, s)
        fmt.Printf("Area = %f, Perimeter = %f\n\n", s.Area(), s.Perimeter())
    
        var s1 Shape = Rectangle{4.0, 6.0}
        fmt.Printf("Shape Type = %T, Shape Value = %v\n", s1, s1)
        fmt.Printf("Area = %f, Perimeter = %f\n", s1.Area(), s1.Perimeter())
    }
    
    # Output
    Shape Type = main.Circle, Shape Value = {5}
    Area = 78.539816, Perimeter = 31.415927
    
    Shape Type = main.Rectangle, Shape Value = {4 6}
    Area = 24.000000, Perimeter = 20.000000
    
  • Use Interface type as arguments to functions

    package main
    
    import (
        "fmt"
    )
    
    // Generic function to calculate the total area of multiple shapes of different types
    func CalculateTotalArea(shapes ...Shape) float64 {
        totalArea := 0.0
        for _, s := range shapes {
            totalArea += s.Area()
        }
        return totalArea
    }
    
    func main() {
        totalArea := CalculateTotalArea(Circle{2}, Rectangle{4, 5}, Circle{10})
        fmt.Println("Total area = ", totalArea)
    }
    
    # Output
    Total area =  346.7256359733385
    
  • Use Interface type as fields

    package main
    
    import (
        "fmt"
    )
    
    // Interface types can also be used as fields
    type MyDrawing struct {
        shapes  []Shape
        bgColor string
        fgColor string
    }
    
    func (drawing MyDrawing) Area() float64 {
        totalArea := 0.0
        for _, s := range drawing.shapes {
            totalArea += s.Area()
        }
        return totalArea        
    }
    
    func main() {
        drawing := MyDrawing{
            shapes: []Shape{
                Circle{2},
                Rectangle{3, 5},
                Rectangle{4, 7},
            },
            bgColor: "red",
            fgColor: "white",
        }
    
        fmt.Println("Drawing", drawing)
        fmt.Println("Drawing Area = ", drawing.Area())
    }
    
    # Output
    Drawing {[{2} {3 5} {4 7}] red white}
    Drawing Area = 55.56637061435917
    

Interface values: How does an interface type work with concrete values?

Under the hood, an interface value can be thought of as a tuple consisting of a value and a concrete type:

// interface
(value, type)

Let’s see an example to understand more:

package main

import (
	"fmt"
)


func main() {
	var s Shape

	s = Circle{5}
	fmt.Printf("(%v, %T)\n", s, s)
	fmt.Printf("Shape area = %v\n", s.Area())

	s = Rectangle{4, 7}
	fmt.Printf("(%v, %T)\n", s, s)
	fmt.Printf("Shape area = %v\n", s.Area())
}
# Output
({5}, main.Circle)
Shape area = 78.53981633974483
({4 7}, main.Rectangle)
Shape area = 28

When we call a method on an interface value, a method of the same name on its underlying type is executed.

Footnotes