Part 18: Interfaces - I

Welcome to tutorial no. 18 in Golang tutorial series. This is the first part in our 2 part interface tutorial.

What is an interface?

The general definition of an interface in the Object oriented world is "interface defines the behaviour of an object". It only specifies what the object is supposed to do. The way of achieving this behaviour (implementation detail) is upto the object.

In Go, an interface is a set of method signatures. When a type provides definition for all the methods in the interface, it is said to implement the interface. It is much similar to the OOP world. Interface specifies what methods a type should have and the type decides how to implement these methods.

For example WashingMachine can be a interface with method signatures Cleaning() and Drying(). Any type which provides definition for Cleaning() and Drying() is said to implement the WashingMachine interface.

Declaring and implementing an interface

Lets dive right into a program which creates a interface and implements it.

package main

import (  
    "fmt"
)

//interface definition
type VowelsFinder interface {  
    FindVowels() []rune
}

type MyString string

//MyString implements VowelsFinder
func (ms MyString) FindVowels() []rune {  
    var vowels []rune
    for _, rune := range ms {
        if rune == 'a' || rune == 'e' || rune == 'i' || rune == 'o' || rune == 'u' {
            vowels = append(vowels, rune)
        }
    }
    return vowels
}

func main() {  
    name := MyString("Sam Anderson")
    var v VowelsFinder
    v = name // possible since MyString implements VowelsFinder
    fmt.Printf("Vowels are %c", v.FindVowels())

}

Run in playground

Line no. 8 of the program above creates a interface type named VowelsFinder which has one method FindVowels() []rune.

In the next line a type MyString is created.

In line no. 15 we add the method FindVowels() []rune to the receiver type MyString. Now MyString is said to implement the interface VowelsFinder. This is quite different from other languages like Java where a class has to explicitly state that it implements a interface using the implements keyword. This is not needed in go and go interfaces are implemented implicitly if a type contains all the methods declared in the interface.

In line no.28, we assign name which is of type MyString to v of type VowelsFinder. This is possible since MyString implements VowelsFinder. v.FindVowels() in the next line calls the FindVowels method on MyString type and prints all the vowels in the string Sam Anderson. This program outputs Vowels are [a e o]

Congrats! You have created and implemented your first interface.

Practical use of interface

The above example taught us how to create and implement interfaces, but it didn't really show the practical use of a interface. Instead of v.FindVowels() if we used name.FindVowels() in the program above, it would have also worked and there would have been no use of the created interface.

So now lets look into a practical use case of interface.

We will write a simple program which calculates the total expense for a company based on the individual salaries of the employees. For brevity we have assumed that all expenses are in USD.

package main

import (  
    "fmt"
)

type SalaryCalculator interface {  
    CalculateSalary() int
}

type Permanent struct {  
    empId    int
    basicpay int
    pf       int
}

type Contract struct {  
    empId  int
    basicpay int
}

//salary of permanent employee is sum of basic pay and pf
func (p Permanent) CalculateSalary() int {  
    return p.basicpay + p.pf
}

//salary of contract employee is the basic pay alone
func (c Contract) CalculateSalary() int {  
    return c.basicpay
}

/*
total expense is calculated by iterating though the SalaryCalculator slice and summing  
the salaries of the individual employees  
*/
func totalExpense(s []SalaryCalculator) {  
    expense := 0
    for _, v := range s {
        expense = expense + v.CalculateSalary()
    }
    fmt.Printf("Total Expense Per Month $%d", expense)
}

func main() {  
    pemp1 := Permanent{1, 5000, 20}
    pemp2 := Permanent{2, 6000, 30}
    cemp1 := Contract{3, 3000}
    employees := []SalaryCalculator{pemp1, pemp2, cemp1}
    totalExpense(employees)

}

Run in playground

Line no. 7 of the above program declares SalaryCalculator interface type with a single method CalculateSalary() int.

We have two kind of employees in the company, Permanent and Contract defined by structs in line no. 11 and 17. The salary of permanent employees is the sum of the basicpay and pf whereas for contract employees its just the basic pay basicpay. This is expressed in the corresponding CalculateSalary methods in line. no 23 and 28 respectively. By declaring this method, both Permanent and Contract now implement the SalaryCalculator interface.

The totalExpense method declared in line no.36 expresses the beauty of using interfaces. This method takes a slice of SalaryCalculator interface []SalaryCalculator as parameter. In line no. 49 we pass a slice which contains both Permanent and Contract types to the totalExpense method. The totalExpense method calculates the expense by calling the CalculateSalary method of the corresponding type. This is done in line. no 39.

The biggest advantage of this is that totalExpense can be extended to any new employee type without any code changes. Lets say the company adds a new type of employee Freelancer with a different salary structure. This Freelancer can just be passed in the slice argument to totalExpense without even a single line of code change to the totalExpense method. This method will do what its supposed to do as Freelancer will also implement the SalaryCalculator interface :).

This program outputs Total Expense Per Month $14050.



Interface internal representation

An interface can be thought of as being represented internally by a tuple (type, value). type is the underlying concrete type of the interface and value holds the value of the concrete type.

Lets write a program to understand better.

package main

import (  
    "fmt"
)

type Test interface {  
    Tester()
}

type MyFloat float64

func (m MyFloat) Tester() {  
    fmt.Println(m)
}

func describe(t Test) {  
    fmt.Printf("Interface type %T value %v\n", t, t)
}

func main() {  
    var t Test
    f := MyFloat(89.7)
    t = f
    describe(t)
    t.Tester()
}

Run in playground

Test interface has one method Tester() and MyFloat type implements that interface. In line no. 24, we assign the variable f of type MyFloat to t which is of type Test. Now the concrete type of t is MyFloat and the value of t is 89.7. The describe function in line no.17 prints the value and concrete type of the interface. This program outputs

Interface type main.MyFloat value 89.7  
89.7  

Empty Interface

An interface which has zero methods is called 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() {  
    s := "Hello World"
    describe(s)
    i := 55
    describe(i)
    strt := struct {
        name string
    }{
        name: "Naveen R",
    }
    describe(strt)
}

Run in playground

In the program above, in line no.7, the describe(i interface{}) function takes a empty interface as argument and hence it can be passed any type.

We pass string, int and struct to the describe function in line nos. 13, 15 and 21 respectively. This program prints,

Type = string, value = Hello World  
Type = int, value = 55  
Type = struct { name string }, value = {Naveen R}  

Type Assertion

Type assertion is used extract the underlying value of the interface.

i.(T) is the syntax which is used to get the underlying value of interface i whose concrete type is T.

A program is worth a thousand words 😀. Lets write one for type assertion.

package main

import (  
    "fmt"
)

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

Run in playground

The concrete type of s in line no. 12 is int. We use the syntax i.(int) in line no. 8 to fetch the underlying int value of i. This program prints 56.

What will happen if the concrete type in the above program is not int? Well lets find out.

package main

import (  
    "fmt"
)

func assert(i interface{}) {  
    s := i.(int) 
    fmt.Println(s)
}
func main() {  
    var s interface{} = "Steven Paul"
    assert(s)
}

Run in playground

In the program above we pass s of concrete type string to the assert function which tries to extract a int value from it. This program will panic with the message panic: interface conversion: interface {} is string, not int.

To solve the above problem, we can use the syntax

v, ok := i.(T)  

If the concrete type of i is T then v will have the underlying value of i and ok will be true.

If the concrete type of i is not T then ok will be false and v will have the zero value of type T and the program will not panic.

package main

import (  
    "fmt"
)

func assert(i interface{}) {  
    v, ok := i.(int)
    fmt.Println(v, ok)
}
func main() {  
    var s interface{} = 56
    assert(s)
    var i interface{} = "Steven Paul"
    assert(i)
}

Run in playground

When Steven Paul is passed to the assert function, ok will be false since the concrete type of i is not int and v will have the value 0 which is the zero value of int. This program will print,

56 true  
0 false  

Type Switch

A type switch is used to the compare the concrete type of an interface against multiple types specified in various case statements. It is similar to switch case. The only difference being the cases specify types and not values as in normal switch.

The syntax for type switch is similar to Type assertion. In the syntax i.(T) for Type assertion, the type T should be replaced by the keyword type for type switch. Lets see how this works in the program below.

package main

import (  
    "fmt"
)

func findType(i interface{}) {  
    switch i.(type) {
    case string:
        fmt.Printf("I am a string and my value is %s\n", i.(string))
    case int:
        fmt.Printf("I am an int and my value is %d\n", i.(int))
    default:
        fmt.Printf("Unknown type\n")
    }
}
func main() {  
    findType("Naveen")
    findType(77)
    findType(89.98)
}

Run in playground

In line no. 8 of the above program, switch i.(type) specifies a type switch. Each of the case statements compare the concrete type of i to a specific type. If any case matches, the corresponding statement is printed. This program outputs,

I am a string and my value is Naveen  
I am an int and my value is 77  
Unknown type  

89.98 in line no. 20 is of type float64 and does not match any of the cases and hence Unknown type is printed in the last line.

It is also possible to compare a type to an interface. If we have a type and if that type implements an interface, it is possible to compare this type with the interface it implements.

Lets write a program for more clarity.

package main

import "fmt"

type Describer interface {  
    Describe()
}
type Person struct {  
    name string
    age  int
}

func (p Person) Describe() {  
    fmt.Printf("%s is %d years old", p.name, p.age)
}

func findType(i interface{}) {  
    switch v := i.(type) {
    case Describer:
        v.Describe()
    default:
        fmt.Printf("unknown type\n")
    }
}

func main() {  
    findType("Naveen")
    p := Person{
        name: "Naveen R",
        age:  25,
    }
    findType(p)
}

Run in background

In the program above, the Person struct implements the Describer interface. In the case statement in line no. 19, v is compared to the Describer interface type. p implements Describer and hence this case is satisfied and Describe() method is called when the control hits findType(p) in line no. 32.

This program outputs

unknown type  
Naveen R is 25 years old  

This brings us to the end of Interfaces Part I. We will continue our discussion about interfaces in Part II. Have a good day.



Next tutorial - Interfaces - II