Golang interface Tutorial [Practical Examples]


GO

Reviewer: Deepak Prasad

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 keyword type followed by the name of the interface and lastly the keyword interface and 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 the fmt package , where fmt.Print()function takes any number of arguments of type interface{} .The zero value of an empty interface in Go is nil as 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 a salaryCalculatorinterface that has two function signatures called calculateSalary() and report() . 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 namely PermanentEmployee   and ContractEmployee  . These two struct types define receiver functions with the same names (calculateSalary & report ) and return type as the once in the salaryCalculator  interface. 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 for PermanentEmployee  and ContractEmployee  have the same signatures but different implementations. Therefore the salary calculation for PermanentEmployee  is different from the ContractEmployee  but 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 type salaryCalculator  . In the next line, we assign the calculator variable to PermanentEmployee struct. This is made possible due to the fact that PermanentEmployee   implements the salaryCalculator  interface.The same applies for the ContractEmployee  struct.When working with the salaryCalculator in our example, we see that PermanentEmployee  and ContractEmployee  are one type because they share same behavior and the salaryCalculator  interface 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 type salaryCalculator  and 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 a salaryExpense()  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  type ReadFileError . ReadFileError has a receiver function called Error() and it returns a string, which makes ReadFileError struct to implement the built-in error interface implicitly. We then define the errorHandler function that takes the built-in error type as its argument. This function only prints messages on the screen by calling err.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 message e := ReadFileError{message: "Error reading file"} then use errorHandler(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

He is highly skilled software developer with expertise in Python, Golang, and AWS cloud services. Skilled in building scalable solutions, he specializes in Django, Flask, Pandas, and NumPy for web apps and data processing, ensuring robust and maintainable code for diverse projects. You can reach out to him on his LinkedIn profile.

Can't find what you're searching for? Let us assist you.

Enter your query below, and we'll provide instant results tailored to your needs.

If my articles on GoLinuxCloud has helped you, kindly consider buying me a coffee as a token of appreciation.

Buy GoLinuxCloud a Coffee

For any other feedbacks or questions you can send mail to admin@golinuxcloud.com

Thank You for your support!!

Leave a Comment