Interfaces in Go
In this tutorial, we will discuss interfaces in the Go language. Go language interfaces are different from other languages.
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}
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
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
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
That’s all about the Interfaces in Go language. If you have any queries or feedback, please write us email at contact@waytoeasylearn.com. Enjoy learning, Enjoy Go language.!!