Interfaces in Go

Interfaces in Go

In this tutorial, we will discuss interfaces in the Go language. Go language interfaces are different from other languages.

Interfaces in Go

In Go language, the interface is a custom type used to specify a set of one or more method signatures, and the interface is abstract, so you are not allowed to create an instance of the interface.

But you are allowed to create a variable of an interface type, and this variable can be assigned with a concrete type value that has the methods the interface requires. In other words, the interface is a collection of methods and a custom type.

An interface in Go is a type defined using a set of method signatures. The interface defines the behaviour for a similar kind of object.

For example, WashingMachine can be an interface with method signatures Cleaning() and Drying(). Any type that defines cleaning () and Drying() methods is said to implement the WashingMachine interface.

An interface is declared using the type keyword, followed by the interface’s name and the keyword interface. Then, we specify a set of method signatures inside curly braces.

type myinterface interface{ 
  fun1() int 
  fun2() float64 
}
Implementing an interface in Go

In Go language, to implement an interface, you just need to implement all the methods declared in the interface.

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

You just implement all the methods declared in the interface and you’re done.

type Shape interface { 
   Area() float64 
   Perimeter() float64 
}

Here are two Struct types that implement the above 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 is not that useful unless we use it with a concrete type that implements all its methods. Let’s see how we use an interface with concrete values.

package main

import (
	"fmt"
)

func main() {
	var s Shape = Circle{10.0}
	fmt.Printf("Shape Type = %T, Shape Value = %v\n", s, s)
	fmt.Printf("Area = %f, Perimeter = %f\n\n", s.Area(), s.Perimeter())

	s = Rectangle{5.0, 6.0}

	fmt.Printf("Shape Type = %T, Shape Value = %v\n", s, s)

	fmt.Printf("Area = %f, Perimeter = %f\n", s.Area(), s.Perimeter())
}

Output

Shape Type = main.Circle, Shape Value = {10} 
Area = 314.159265, Perimeter = 62.831853 
Shape Type = main.Rectangle, Shape Value = {5 6} 
Area = 30.000000, Perimeter = 22.000000
Using Interface types 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
Empty interface

An interface that has zero methods is called an empty interface. It is represented as interface{}. Since the empty interface has zero methods, all types implement the empty interface.

package main

import "fmt"

func describe(i interface{}) {
	fmt.Printf("Type = %T, value = %v\n", i, i)
}

func main() {
	name := "Ashok Kumar"
	describe(name)

	age := 29
	describe(age)

	strt := struct {
		salary int
	}{
		salary: 50000,
	}
	describe(strt)
}

Output

Type = string, value = Ashok Kumar 
Type = int, value = 29 
Type = struct { salary int }, value = {50000} 

Run in playground

Type assertion

Type assertion is used to extract the underlying value of the interface. i.(T) is the syntax that is used to get the underlying value of interface i whose concrete type is T.

A program is worth a thousand words 😀. Let’s write one for type assertion.

package main

import (
    "fmt"
)

func assert(i interface{}) {
    s := i.(string) //get the underlying int value from i
    fmt.Println(s)
}

func main() {
    var name interface{} = "Ashok Kumar"
    assert(name)
}

Output

Ashok Kumar

Run in playground

What will happen if the concrete type in the above program is not string? Well, let’s find out.

package main

import (
    "fmt"
)

func assert(i interface{}) {
    s := i.(string)
    fmt.Println(s)
}

func main() {
    var age interface{} = 29
    assert(age)
}

Output

panic: interface conversion: interface {} is int, not string
Why Interface?

Following are some benefits of using the interface.

1. Helps write more modular and decoupled code between different parts of the codebase – It can help reduce dependency between different parts of the codebase and provide loose coupling.

For example, imagine an application interacting with a database layer. If the application interacts with the database using the interface, it never knows what kind of database is being used in the background.

You can change the type of database in the background. Let’s say from ArangoDB to Mongo DB without any change in the application layer. It interacts with the database layer via an interface that both ArangoDB and Mongo DB implement.

2. We can use an interface to achieve run time polymorphism in the Go language. Runtime Polymorphism means that a call is resolved at runtime. Let’s understand how can we use the interface to achieve runtime polymorphism with an example

Different countries have different ways of calculating the tax. This can be represented using an interface.

type taxCalculator interface{
    calculateTax()
}

Now different countries can have their own struct and can implement the calculateTax() method. The same calculateTax method is used in different contexts to calculate tax. When the compiler sees this call, it delays which exact method to be called at run time.

package main

import "fmt"

type taxSystem interface {
	calculateTax() int
}
type indianTax struct {
	taxPercentage int
	income        int
}

func (i *indianTax) calculateTax() int {
	tax := i.income * i.taxPercentage / 100
	return tax
}

type singaporeTax struct {
	taxPercentage int
	income        int
}

func (i *singaporeTax) calculateTax() int {
	tax := i.income * i.taxPercentage / 100
	return tax
}

type usaTax struct {
	taxPercentage int
	income        int
}

func (i *usaTax) calculateTax() int {
	tax := i.income * i.taxPercentage / 100
	return tax
}
func main() {
	indianTax := &indianTax{
		taxPercentage: 30,
		income:        1000,
	}
	singaporeTax := &singaporeTax{
		taxPercentage: 10,
		income:        2000,
	}

	taxSystems := []taxSystem{indianTax, singaporeTax}
	totalTax := calculateTotalTax(taxSystems)

	fmt.Printf("Total Tax is %d\n", totalTax)
}
func calculateTotalTax(taxSystems []taxSystem) int {
	totalTax := 0
	for _, t := range taxSystems {
		totalTax += t.calculateTax() //This is where runtime polymorphism happens
	}
	return totalTax
}

Output

Total Tax is 500

Run in playground

Now following is the line where run time polymorphism happens.

 totalTax += t.calculateTax() //This is where runtime polymorphism happens

The correct calculateTax() method is called based upon weather the instance is of type singaporeTax struct tax or indianTax struct.

Zero Value of Interface in Go

The default or zero value of an interface is nil. The following program demonstrates that

package main

import "fmt"

type employee interface {
	getName()
	getSalary()
}

func main() {
	var emp employee
	fmt.Println(emp)
}

Output

nil

Run in playground

Interfaces in Go

Scroll to top