Interfaces in GoLang

In general programming interfaces are contracts that have a set of functions to be implemented to fulfill that contract. Go is no different. Go has great support for interfaces and they are implemented in an implicit way. They allow polymorphism in Go. In this post, we will talk about interfaces, what they are, and how they can be used.

What is an Interface?

An interface is an abstract concept which enables polymorphism in Go. A variable of that interface can hold the value that implements the type. Type assertion is used to get the underlying concrete value as we will see in this post.

Declaring an interface in GoLang

An interface is declared as a type. Here is the declaration that is used to declare an interface.

type interfaceName interface{}

Zero-value of an interface

The zero value of an interface is nil. That means it holds no value and type. The code below shows that.

package main

import (
	"fmt"
)

func main() {
	var a interface{}
	
	fmt.Println(a)    // <nil>
}

The empty interface in Go

An interface is empty if it has no functions at all. An empty interface holds any type. That’s why it is extremely useful in many cases. Below is the declaration of an empty interface.

var i interface{}

Implementing an interface in GoLang

An interface is implemented when the type has implemented the functions of the interface. Here is an example showing how to implement an interface.

package main

import (
	"fmt"
)

type Person interface{
	greet() string
}

type Human struct{
	Name string
}

func (h *Human)greet() string {
	return "Hi, I am " + h.Name 
}

func isAPerson(h Person) {
	fmt.Println(h.greet())
}

func main() {
	var a = Human{"John"}
	
	fmt.Println(a.greet())    // Hi, I am John
	
	// below function will only work
	// if a is also a person.
	// Here we can see polymorphism in action.
	isAPerson(&amp;a)             // Hi, I am John      
}

Implementing multiple interfaces in Go

Multiple interfaces can be implemented at the same time. If all the functions are all implemented then the type implements all the interfaces. Below the type, the bird type implements both the interfaces by implementing the functions.

package main

import (
	"fmt"
	"reflect"
)

type Flyer interface{
	fly() string
}

type Walker interface{
	walk() string
}

type Bird struct{
	Name string
}

func (b *Bird)fly() string{
	return "Flying..."
}

func (b *Bird)walk() string{
	return "Walking..."
}

func main() {
  	var b = Bird{"Chirper"}
	
	fmt.Println(b.fly())     // Flying...
	fmt.Println(b.walk())    // Walking...	
}

Composing interfaces together

Interfaces can be composed together. The composition is one of the most important concepts in software development. When multiple interfaces are implemented then the type has performed composition. This is really helpful where polymorphism is needed.

Values in an interface

Interface values have a concrete value and a dynamic type.

package main

import (
	"fmt"
)

type Flyer interface{
	fly() string
}

type Walker interface{
	walk() string
}

type Bird struct{
	Name string
}

func (b *Bird)fly() string{
	return "Flying..."
}

func (b *Bird)walk() string{
	return "Walking..."
}

func main() {
  	var b = Bird{"Chirper"}
        // %v for value and %T for type
	fmt.Printf("%v --> %T", b, b)    // {Chirper} --> main.Bird
}

In the code above chirper is of type Bird but has a concrete value of {Chirpir}.

Type assertion using the interface

Type assertion is a way to get the underlying value an interface holds. This means if an interface variable is assigned a string then the underlying value it holds is the string. Here is an example showing how to use type assertion using interfaces.

package main

import (
	"fmt"
)

type B struct{
	s string
}

func main() {
	var i interface{} = B{"a sample string"}
	
	fmt.Println(i.(B)) // {a sample string}
}

Type switch using an interface

Type switches are an extremely similar control structure like the switch-cases, the only difference is here the interface type is used to switch between different conditions.

package main

import (
	"fmt"
)

func checkType(i interface{}) {
	switch i.(type) {          // the switch uses the type of the interface
	case int:
		fmt.Println("Int")
	case string:
		fmt.Println("String")
	default:
		fmt.Println("Other")
	}
}

func main() {
	var i interface{} = "A string"
	
	checkType(i)   // String
}

Equality of interface values

The interface values can be equal if any of the conditions shown below are true.

  • They both are nil.
  • They have the same underlying concrete values and the same dynamic type.
package main

import (
	"fmt"
)

func isEqual(i interface{}, j interface{}) {
	if(i == j) {
		fmt.Println("Equal")
	} else {
		fmt.Println("Inequal")
	}
}

func main() {
	var i interface{}
	var j interface{}
	
	isEqual(i, j)   // Equal
	
	var a interface{} = "A string"
	var b interface{} = "A string"
	
	isEqual(a, b)   // Equal
}

Using interfaces with functions

Interfaces can be passed to functions just like any other type. Here is an example showing the usage of the interface with functions. A great advantage when using an interface is that it allows any type of argument as we can see in this code below.

package main

import (
	"fmt"
)

func f(i interface{}) {
	fmt.Printf("%T\n", i)
}

func main() {
	var a interface{} = "a string"
	var c int = 42
	
	f(a)   // string
	f(c)   // int
}

Uses of an interface

Interfaces are used in Go where polymorphism is needed. In a function where multiple types can be passed an interface can be used. Interfaces allow Go to have polymorphism.

Interfaces are a great feature in Go and should be used wisely.