My traditional understanding of interfaces was for Polymorphism, i.e where a object of a certain type (as determined by it’s class inheritance) could also be a different type as determined by an interface so long as it implemented the methods in that interface. The concept is similar in golang albeit more lightweight. For example, let’s say I declare two struct types called Circle and Square as follows:

package main

type Square struct { 
  Length float64 
  Width float64 
} 

type Circle struct { 
  Radius float64 
}

I need a function for each of these types to calculate their area. Because calculating the area of a square is different from calculating the area of a circle these functions are specific to each type, so we’ll use receiver functions to attach an Area to each type.

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


func (s *Square) Area() float64 { 
  return s.Length * s.Width 
} 

I can call each function to calculate the area as follows:

func main() { 
  s := &Square{17, 16} 
  c := &Circle{15} 

  fmt.Printf("The area of s is %f\n", s.Area()) 
  fmt.Printf("The area of c is %f\n", c.Area()) 
} 

So how do interfaces come into play?. Well, what if I want to add the two areas together?. The ideal solution for this would be to send an array of all shapes, iterate through them to call the Area method on each shape, add the results and return the value. The problem is, we cannot have an array (slice) containing different types. What we can do however is define an interface. This interface would be a list of methods that are common to the shapes, i.e both our Circle and Square types have a Area method, so we can define a new interface which declares this method as follows:

type Shape interface { 
  Area() float64 
} 

With this we now have a new type called Shape. Any existing type that implements the Area() method will also be of type Shape (plus it's existing type - i.e. just like traditional Polymorphism, i.e. my existing types of Circle and Square are now also of type Shape. Now we can build an array (slice) of Shapes and iterate through them to call the Area() function and add there areas:

func sumAreas(shapes []Shape) float64 { 
  total := 0.0 

  for _, shape := range shapes { 
    total += shape.Area() 
  } 

  return total 
} 

Now let’s update the main function as follows:

func main() { 
  s := &Square{17, 16} 
  c := &Circle{15} 

  fmt.Printf("The area of s is %f\n", s.Area()) 
  fmt.Printf("The area of c is %f\n", c.Area()) 

  shapes := []Shape{s,c} 
  sumOfAreas := sumAreas(shapes) 
  fmt.Printf(“The sum of areas is: %f\n”, sumOfAreas) 
} 

Here's the full code:

package main

import (
  "fmt"
  "math"
)

type Square struct {
  Length float64
  Width  float64
}

type Circle struct {
  Radius float64
}

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

func (s *Square) Area() float64 {
  return s.Length * s.Width
}

type Shape interface {
  Area() float64
}

func sumAreas(shapes []Shape) float64 {
  total := 0.0
  for _, shape := range shapes {
    total += shape.Area()
  }
  return total
}

func main() {
  s := &Square{17, 16}
  c := &Circle{15}
  fmt.Printf("The area of s is %f\n", s.Area())
  fmt.Printf("The area of c is %f\n", c.Area())
  shapes := []Shape{s, c}
  sumOfAreas := sumAreas(shapes)
  fmt.Printf("The sum of areas is: %f\n", sumOfAreas)
}