Golang interface Tutorial [Practical Examples]

Golang interface Tutorial [Practical Examples]

Getting started with golang interfaces

In Go , interfaces are custom types that define a set of function signatures. By design , interfaces are considered to be tools that make code clean, short and provide a good API between packages, servers and clients. Go interfaces enable developers to treat other types as one type temporarily as long as these types have the functions defined in the interface. When a type has the functions defined in an interface, we say that that type implements that interface.

When using interfaces , you are not allowed to create an instance of an interface, but rather, you can create a variable of an interface type and this variable can be assigned to a value type that implements the interface.

Therefore Go interfaces are super important for Go developers to have in their tool belt. In this article we will learn about Go interfaces and therefore it’s a prerequisite that you have Go runtime installed in your computer to be able to proceed without any problems.


Golang interface syntax

It’s important to remember that Go interfaces are simply a collection of named function signatures wrapped inside a custom type(interface).To define an interface use the keywordtype followed by the name of the interface and lastly the keywordinterfaceand curly braces. Using your preferred code editor, navigate to your working directory and create a folder called interfaces and add a main.go file in your interface directory and add the below code.

package main
type salaryCalculator interface{}

When naming your interface, the name should describe what the interface is doing. It has become a popular thing in the Go community to use the “er” suffix to the interface name where possible.


Empty interface

An empty interface is an interface that does not specify methods in it. This means that an empty interface may hold values of any type. Therefore it’s worth noting that in Go , every type implements at least zero methods. Empty interfaces are useful with code that handles values of unknown type. A good example is from thefmt package, wherefmt.Print()function takes any number of arguments of typeinterface{}.The zero value of an empty interface in Go isnilas shown below.

Example

package main

import "fmt"

type salaryCalculator interface{}

func main() {
   var s salaryCalculator
   fmt.Println(s)
}

Output

$ go run main.go
<nil>

Interface with methods set

To add a named function signature, add the name of the function followed by round parenthesis and a return value if the function has one. Our interface has no function signature in it. To add a named method signature, add the name of the function followed by round parenthesis and a return value if the function has one.

Example

package main

type salaryCalculator interface {
   calculateSalary() float64
   report()
}

Explanation

In the above example, we have defined asalaryCalculatorinterface that has two function signatures calledcalculateSalary()andreport(). To use (implement) the interface, we need to define a type that has the two functions. It’s common to define type structs in Go to implement interfaces, but other types also like string can also be used with interfaces.


Implementing an interface in golang

Example-1

In the next example, we have defined two structs namelyPermanentEmployeeandContractEmployee. These two struct types define receiver functions with the same names (calculateSalary & report ) and return type as the once in thesalaryCalculatorinterface. By defining receiver functions with the same name and return types as the once in the interface, will implements the interface implicitly. This is a unique way in Go that allows developers to implement interfaces without using keywords like “implement” . The receiver functions forPermanentEmployeeandContractEmployeehave the same signatures but different implementations. Therefore the salary calculation forPermanentEmployeeis different from theContractEmployeebut they all perform the same task.

package main

import "fmt"

type salaryCalculator interface {
   calculateSalary() float64
   report()
}

type PermanentEmployee struct {
   id          int
   basicSalary float64
   commission  float64
}

type ContractEmployee struct {
   id          int
   basicSalary float64
}

func (p PermanentEmployee) calculateSalary() float64 {
   return p.basicSalary + (p.commission/100)*p.basicSalary
}

func (c ContractEmployee) calculateSalary() float64 {
   return c.basicSalary
}

func (p PermanentEmployee) report() {
   fmt.Printf("Employee ID %d earns USD %f per month \n", p.id, p.calculateSalary())
}

func (c ContractEmployee) report() {
   fmt.Printf("Employee ID %d earns USD %f per month \n", c.id, c.calculateSalary())
}

func main() {
   var calculator salaryCalculator
   calculator = PermanentEmployee{id: 1, basicSalary: 10000, commission: 20}
   calculator.report()
   calculator = ContractEmployee{id: 2, basicSalary: 5000}
   calculator.report()
}

Output:

$ go run main.go
Employee ID 1 earns USD 12000.000000 per month
Employee ID 2 earns USD 5000.000000 per month

Explanation:

In our main function, we define a calculator variable of typesalaryCalculator. In the next line, we assign the calculator variable toPermanentEmployee struct. This is made possible due to the fact thatPermanentEmployeeimplements thesalaryCalculatorinterface.The same applies for theContractEmployeestruct.When working with thesalaryCalculatorin our example, we see thatPermanentEmployeeandContractEmployeeare one type because they share same behavior and thesalaryCalculatorinterface links them up. The output on the terminal prints a message for each employee. But how is this useful? Imagine you have a slice of employees , both permanent and contract , and the company they work for wants to calculate the total salary that will be paid at the end of the month. It’s true that it’s hectic to loop through all employees and do the math on the fly while in the loop.

Example-2

To solve this , we can create a slice of typesalaryCalculatorand add employees in the slice , loop through them and calculate the total salary.

Example

package main

import "fmt"

type salaryCalculator interface {
   calculateSalary() float64
   report()
}

type PermanentEmployee struct {
   id          int
   basicSalary float64
   commission  float64
}

type ContractEmployee struct {
   id          int
   basicSalary float64
}

func (p PermanentEmployee) calculateSalary() float64 {
   return p.basicSalary + (p.commission/100)*p.basicSalary
}

func (c ContractEmployee) calculateSalary() float64 {
   return c.basicSalary
}

func (p PermanentEmployee) report() {
   fmt.Printf("Employee ID %d earns USD %f per month \n", p.id, p.calculateSalary())
}

func (c ContractEmployee) report() {
   fmt.Printf("Employee ID %d earns USD %f per month \n", c.id, c.calculateSalary())
}

func main() {
   p1 := PermanentEmployee{id: 1, basicSalary: 2300, commission: 13}
   p2 := PermanentEmployee{id: 2, basicSalary: 1500, commission: 18}
   p3 := PermanentEmployee{id: 3, basicSalary: 2300, commission: 10}
   c1 := ContractEmployee{id: 4, basicSalary: 500}
   c2 := ContractEmployee{id: 5, basicSalary: 1100}
   c3 := ContractEmployee{id: 6, basicSalary: 700}

   employees := []salaryCalculator{p1, p2, p3, c1, c2, c3}

   var totalSalary float64

   for _, employee := range employees {
       totalSalary += employee.calculateSalary()
   }
   fmt.Printf("Company total salary is : USD %f", totalSalary)
}

Output

$ go run main.go
Company total salary is USD 9199.000000

Example-3

To make our code shorter and cleaner, we can define asalaryExpense() function that takes a slice of salaryCalculator interface as an argument, and return the total salary. Add the below code to the existing code.

Example

func salaryExpense(s []salaryCalculator) float64 {
   var totalSalary float64
   for _, employee := range s {
       totalSalary += employee.calculateSalary()
   }
   return totalSalary
}

func main() {
   p1 := PermanentEmployee{id: 1, basicSalary: 2300, commission: 13}
   p2 := PermanentEmployee{id: 2, basicSalary: 1500, commission: 18}
   p3 := PermanentEmployee{id: 3, basicSalary: 2300, commission: 10}
   c1 := ContractEmployee{id: 4, basicSalary: 500}
   c2 := ContractEmployee{id: 5, basicSalary: 1100}
   c3 := ContractEmployee{id: 6, basicSalary: 700}

   employees := []salaryCalculator{p1, p2, p3, c1, c2, c3}
   totalSalary := salaryExpense(employees)
   fmt.Printf("Company total salary is USD %f \n", totalSalary)
}

Output

$ go run main.go
Company total salary is USD 9199.000000

Example-4: Create built-in error interface

erroris a built-in interface type in Go. A variable from the error interface represents any value that describes itself as a string.The error type has a single method called Error(). In Go , the error interface is declared as below.

type error interface {
   Error() string
}

Go does not have the try catch error handling mechanism, and the way around that is to handle errors as they appear. For example, when working with files in Go, it is good to check if the file can be read from. In this scenario, we will use the built-in error interface to handle the error. In the next example, we will create our custom type that implements the error built-in type.

Example

package main

import (
   "fmt"
   "os"
)

type ReadFileError struct {
   message string
}

func (rfe ReadFileError) Error() string {
   return fmt.Sprintf("Custom Read File Error! Error Message : %s", rfe.message)
}

func errorHandler(err error) {
   fmt.Println(err.Error())
}

func main() {
   _, err := os.ReadFile("logs.txt")

   if err != nil {
       e := ReadFileError{message: "Error reading file"}
       errorHandler(e)
       return
   }
   fmt.Println("Reading data from file")
}

Explanation

In our example, we have a created custom struct typeReadFileError.ReadFileError has a receiver function calledError()and it returns a string, which makesReadFileErrorstruct to implement the built-in error interface implicitly. We then define theerrorHandler function that takes the built-in error type as its argument. This function only prints messages on the screen by callingerr.Error()function.In the main function, we try to read a file called“logs.txt” that does not exist in our root folder. This will automatically return an error. We handle the error by creating our custom messagee := ReadFileError{message: "Error reading file"} then useerrorHandler(e)To print the error in the terminal.

Output

$ go run main.go
Custom Read File Error! Error Message : Error reading file

Summary

Go interface is an important tool to use as Go developer. Interfaces in Go enables developers to group together types as long as these types have the same methods defined in an interface that they are implementing. Many times Go interfaces are used to force encapsulation and allow for a versatile, clean and short code base.


References

https://go.dev/tour/methods/9
https://gobyexample.com/interfaces

Related Keywords: golang interface, go interface, interface golang, interfaces in golang, interface in go, golang interface type, golang interfaces

Antony Shikubu

Antony Shikubu

Systems Integration Engineer

Highly skilled software developer with expertise in Python, Golang, and AWS cloud services.