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?

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 an interface with method signatures Cleaning() and Drying(). Any type which provides definition for Cleaning() and Drying() methods is said to implement the WashingMachine interface.

Declaring and implementing an interface

Let’s dive right into a program that creates an interface and implements it.

 1package main
 2
 3import (
 4	"fmt"
 5)
 6
 7//interface definition
 8type VowelsFinder interface {
 9	FindVowels() []rune
10}
11
12type MyString string
13
14//MyString implements VowelsFinder
15func (ms MyString) FindVowels() []rune {
16	var vowels []rune
17	for _, rune := range ms {
18		if rune == 'a' || rune == 'e' || rune == 'i' || rune == 'o' || rune == 'u' {
19			vowels = append(vowels, rune)
20		}
21	}
22	return vowels
23}
24
25func main() {
26	name := MyString("Sam Anderson")
27	var v VowelsFinder
28	v = name // possible since MyString implements VowelsFinder
29	fmt.Printf("Vowels are %c", v.FindVowels())
30
31}

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 an 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 the VowelsFinder interface. 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 an interface

The above example taught us how to create and implement interfaces, but it didn’t really show the practical use of an 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 for interface.

Now let’s look at a practical use of interface.

We will write a simple program that 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.

 1package main
 2
 3import (
 4	"fmt"
 5)
 6
 7type SalaryCalculator interface {
 8	CalculateSalary() int
 9}
10
11type Permanent struct {
12	empId    int
13	basicpay int
14	pf       int
15}
16
17type Contract struct {
18	empId    int
19	basicpay int
20}
21
22//salary of permanent employee is the sum of basic pay and pf
23func (p Permanent) CalculateSalary() int {
24	return p.basicpay + p.pf
25}
26
27//salary of contract employee is the basic pay alone
28func (c Contract) CalculateSalary() int {
29	return c.basicpay
30}
31
32/*
33total expense is calculated by iterating through the SalaryCalculator slice and summing
34the salaries of the individual employees
35*/
36func totalExpense(s []SalaryCalculator) {
37	expense := 0
38	for _, v := range s {
39		expense = expense + v.CalculateSalary()
40	}
41	fmt.Printf("Total Expense Per Month $%d", expense)
42}
43
44func main() {
45	pemp1 := Permanent{
46		empId:    1,
47		basicpay: 5000,
48		pf:       20,
49	}
50	pemp2 := Permanent{
51		empId:    2,
52		basicpay: 6000,
53		pf:       30,
54	}
55	cemp1 := Contract{
56		empId:    3,
57		basicpay: 3000,
58	}
59	employees := []SalaryCalculator{pemp1, pemp2, cemp1}
60	totalExpense(employees)
61
62}

Run in playground

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

We have two kinds 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 it’s 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 structs now implement the SalaryCalculator interface.

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

The program outputs

Total Expense Per Month $14050

The biggest advantage of this is that totalExpense can be extended to any new employee type without any code changes. Let’s 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 function. This method will do what it’s supposed to do as Freelancer will also implement the SalaryCalculator interface :).

Let’s modify this program and add the new Freelancer employee. Salary for the Freelancer is the product of rate per hour and total no of hours worked.

 1package main
 2
 3import (
 4	"fmt"
 5)
 6
 7type SalaryCalculator interface {
 8	CalculateSalary() int
 9}
10
11type Permanent struct {
12	empId    int
13	basicpay int
14	pf       int
15}
16
17type Contract struct {
18	empId    int
19	basicpay int
20}
21
22type Freelancer struct {
23	empId       int
24	ratePerHour int
25	totalHours  int
26}
27
28//salary of permanent employee is sum of basic pay and pf
29func (p Permanent) CalculateSalary() int {
30	return p.basicpay + p.pf
31}
32
33//salary of contract employee is the basic pay alone
34func (c Contract) CalculateSalary() int {
35	return c.basicpay
36}
37
38//salary of freelancer
39func (f Freelancer) CalculateSalary() int {
40	return f.ratePerHour * f.totalHours
41}
42
43/*
44total expense is calculated by iterating through the SalaryCalculator slice and summing
45the salaries of the individual employees
46*/
47func totalExpense(s []SalaryCalculator) {
48	expense := 0
49	for _, v := range s {
50		expense = expense + v.CalculateSalary()
51	}
52	fmt.Printf("Total Expense Per Month $%d", expense)
53}
54
55func main() {
56	pemp1 := Permanent{
57		empId:    1,
58		basicpay: 5000,
59		pf:       20,
60	}
61	pemp2 := Permanent{
62		empId:    2,
63		basicpay: 6000,
64		pf:       30,
65	}
66	cemp1 := Contract{
67		empId:    3,
68		basicpay: 3000,
69	}
70	freelancer1 := Freelancer{
71		empId:       4,
72		ratePerHour: 70,
73		totalHours:  120,
74	}
75	freelancer2 := Freelancer{
76		empId:       5,
77		ratePerHour: 100,
78		totalHours:  100,
79	}
80	employees := []SalaryCalculator{pemp1, pemp2, cemp1, freelancer1, freelancer2}
81	totalExpense(employees)
82
83}

Run in playground

We have added the Freelancer struct in line no. 22 and declared the CalculateSalary method in line no. 39. No other code change is required in the totalExpense method since Freelancer struct also implements the SalaryCalculator interface. We added a couple of Freelancer employees in the main method. This program prints,

Total Expense Per Month $32450

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.

Let’s write a program to understand better.

 1package main
 2
 3import (
 4	"fmt"
 5)
 6
 7type Worker interface {
 8	Work()
 9}
10
11type Person struct {
12	name string
13}
14
15func (p Person) Work() {
16	fmt.Println(p.name, "is working")
17}
18
19func describe(w Worker) {
20	fmt.Printf("Interface type %T value %v\n", w, w)
21}
22
23func main() {
24	p := Person{
25		name: "Naveen",
26	}
27	var w Worker = p
28	describe(w)
29	w.Work()
30}

Run in playground

Worker interface has one method Work() and Person struct type implements that interface. In line no. 27, we assign the variable p of type Person to w which is of type Worker. Now the concrete type of w is Person and it contains a Person with name field Naveen. The describe function in line no.17 prints the value and concrete type of the interface. This program outputs

Interface type main.Person value {Naveen}
Naveen is working

We discuss more on how to extract the underlying value of the interface in the upcoming sections.

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.

 1package main
 2
 3import (
 4	"fmt"
 5)
 6
 7func describe(i interface{}) {
 8	fmt.Printf("Type = %T, value = %v\n", i, i)
 9}
10
11func main() {
12	s := "Hello World"
13	describe(s)
14	i := 55
15	describe(i)
16	strt := struct {
17		name string
18	}{
19		name: "Naveen R",
20	}
21	describe(strt)
22}

Run in playground

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

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 to 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 😀. Let’s write one for type assertion.

 1package main
 2
 3import (
 4	"fmt"
 5)
 6
 7func assert(i interface{}) {
 8	s := i.(int) //get the underlying int value from i
 9	fmt.Println(s)
10}
11func main() {
12	var s interface{} = 56
13	assert(s)
14}

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, let’s find out.

 1package main
 2
 3import (
 4	"fmt"
 5)
 6
 7func assert(i interface{}) {
 8	s := i.(int) 
 9	fmt.Println(s)
10}
11func main() {
12	var s interface{} = "Steven Paul"
13	assert(s)
14}

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.

 1package main
 2
 3import (
 4	"fmt"
 5)
 6
 7func assert(i interface{}) {
 8	v, ok := i.(int)
 9	fmt.Println(v, ok)
10}
11func main() {
12	var s interface{} = 56
13	assert(s)
14	var i interface{} = "Steven Paul"
15	assert(i)
16}

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 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. Let’s see how this works in the program below.

 1package main
 2
 3import (
 4	"fmt"
 5)
 6
 7func findType(i interface{}) {
 8	switch i.(type) {
 9	case string:
10		fmt.Printf("I am a string and my value is %s\n", i.(string))
11	case int:
12		fmt.Printf("I am an int and my value is %d\n", i.(int))
13	default:
14		fmt.Printf("Unknown type\n")
15	}
16}
17func main() {
18	findType("Naveen")
19	findType(77)
20	findType(89.98)
21}

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.

Let’s write a program for more clarity.

 1package main
 2
 3import "fmt"
 4
 5type Describer interface {
 6	Describe()
 7}
 8type Person struct {
 9	name string
10	age  int
11}
12
13func (p Person) Describe() {
14	fmt.Printf("%s is %d years old", p.name, p.age)
15}
16
17func findType(i interface{}) {
18	switch v := i.(type) {
19	case Describer:
20		v.Describe()
21	default:
22		fmt.Printf("unknown type\n")
23	}
24}
25
26func main() {
27	findType("Naveen")
28	p := Person{
29		name: "Naveen R",
30		age:  25,
31	}
32	findType(p)
33}

Run in playground

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.

This program prints

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. I hope you liked this tutorial. Please leave your feedback and comments. Please consider sharing this tutorial on twitter and LinkedIn. Have a good day.

Next tutorial - Interfaces - II