Interfaces in Go

Interfaces in Go do not enforce a type to implement methods but interfaces are very powerful tools. A type can choose to implement methods of an interface. Using interfaces, a value can be represented in multiple types, AKA, polymorphism.

What is an interface?

We talked a lot about the object and behavior in the structs and methods lessons. We also saw how a structure (and other types) can implement methods. An interface is another piece of a puzzle that brings Go close to the Object-Oriented programming paradigm.

An interface is a collection of method signatures that a Type can implement (using methods). Hence interface defines (not declares) the behavior of the object (of the type Type).

For example, a Dog can walk and bark. If an interface defines method signatures for walk and bark while Dog implements walk and bark methods, then Dog is said to implement that interface.

The primary job of an interface is to provide only method signatures consisting of the method name, input arguments and return types. It is up to a Type (e.g. struct type) to declare methods and implement them.

If you are an OOP programmer, you might have used implement keyword a lot while implementing an interface. But in Go, you do not explicitly mention if a type implements an interface.

If a type implements a method with name and signature defined in an interface, then that type implements that interface. Like saying if it walks like a duck, swims like a duck and quacks like a duck, then it’s a duck (ref).

Declaring interface

Like struct, we need to create a derived type to simplify interface declaration using the keyword interface.

type Shape interface {
    Area() float64
    Perimeter() float64
}

💡 The naming convention of interfaces in Go is a little bit tricky, you can follow this Medium article for more information. If you want to go with I prefix or Interface suffix, then that’s fine too. Just keep it consistent.

In the above example, we have defined the Shape interface which has two methods Area and Perimeter that accepts no arguments and return float64 value. Any type that implements these methods (with exact method signatures) will also implement Shape interface.

Since the interface is a type just like a struct, we can create a variable of its type. In the above case, we can create a variable s of type interface Shape.

💡 Like a struct type, there is absolutely no need to create a derived type for an interface. But by doing so, we are making our life miserable.

package main

import "fmt"

type Shape interface {
	Area() float64
	Perimeter() float64
}

func main() {
	var s Shape
	fmt.Println("value of s is", s)
	fmt.Printf("type of s is %T\n", s)
}

// value of s is <nil>
// type of s is <nil>

There is a lot going on in the above example, so let me explain a few concepts about interfaces. An interface has two types. The static type of an interface is the interface itself, for example Shape in the above program. An interface does not have a static value, rather it points to a dynamic value.

A variable of an interface type can hold a value of a type that implements the interface. The value of that type becomes the dynamic value of the interface and that type becomes the dynamic type of the interface.

From the above example, we can see that zero value and type of the interface is nil. This is because, at this moment, we have declared the variable s of type Shape but did not assign any value.

When we use Println function from fmt package with interface argument, it points to the dynamic value of the interface and %T syntax in Printf function refers to the dynamic type of interface.

💡 But in reality, type of interface s is Shape which is its static type.

Implementing interface

Let’s declare Area and Perimeter methods with signatures provided by the Shape interface. Also, let’s create Shape struct and make it implement Shape interface (by implementing these methods).

package main

import "fmt"

type Shape interface {
	Area() float64
	Perimeter() float64
}

type Rect struct {
	width  float64
	height float64
}

func (r Rect) Area() float64 {
	return r.width * r.height
}

func (r Rect) Perimeter() float64 {
	return 2 * (r.width + r.height)
}

func main() {
	var s Shape
	s = Rect{5.0, 4.0}
	r := Rect{5.0, 4.0}
	fmt.Printf("type of s is %T\n", s)
	fmt.Printf("value of s is %v\n", s)
	fmt.Println("area of rectange s", s.Area())
	fmt.Println("s == r is", s == r)
}

// type of s is main.Rect
// value of s is {5 4}
// area of rectange s 20
// s == r is true

In the above program, we’ve created the Shape interface and the struct type Rect. Then we defined methods like Area and Perimeter which belongs to Rect type, therefore Rect implemented those methods.

Since these methods are defined by the Shape interface, the Rect struct type implements the Shape interface. Since we haven’t forced Rect to implement the Shape interface, it is all happening automatically. Hence, we can say that interfaces in Go are implicitly implemented.

When a type implements an interface, a variable of that type can also be represented as the type of an interface. We can confirm that by creating a nil interface s of type Shape and assign a struct of type Rect.

💡 We have just achieved polymorphism.

Since Rect implements Shape interface, this is perfectly valid. From the above result, we can see that, dynamic type of s is now Rect and dynamic value of s is the value of the struct Rect which is {5 4}.

💡 We call it dynamic because we can assign s with a new struct of a different struct type which also implements the interface Shape.

Sometimes, the dynamic type of interface also called a concrete type because when we access type of interface, it returns the type of its underlying dynamic value and its static type remains hidden.

We can call Area method on s since interface Shape defines the Area method and the concrete type of s is Rect which implements the Area method. This method will be called on the dynamic value interface s holds.

Also, we can see that we can compare s with r since both of these variable holds the same dynamic type (struct of type Rect) and dynamic value {5 4}.

💡 You can learn about struct comparison from the structures lesson.

Let’s change the dynamic type and dynamic value of s.

package main

import (
	"fmt"
	"math"
)

type Shape interface {
	Area() float64
	Perimeter() float64
}

type Rect struct {
	width  float64
	height float64
}

type Circle struct {
	radius float64
}

func (r Rect) Area() float64 {
	return r.width * r.height
}

func (r Rect) Perimeter() float64 {
	return 2 * (r.width + r.height)
}

func (c Circle) Area() float64 {
	return math.Pi * c.radius * c.radius
}

func (c Circle) Perimeter() float64 {
	return 2 * math.Pi * c.radius
}

func main() {
	var s Shape = Rect{10, 3}

	fmt.Printf("type of s is %T\n", s)
	fmt.Printf("value of s is %v\n", s)
	fmt.Printf("value of s is %0.2f\n\n", s.Area())

	s = Circle{10}
	fmt.Printf("type of s is %T\n", s)
	fmt.Printf("value of s is %v\n", s)
	fmt.Printf("value of s is %0.2f\n", s.Area())
}

// type of s is main.Rect
// value of s is {10 3}
// value of s is 30.00

// type of s is main.Circle
// value of s is {10}
// value of s is 314.16

If you read structures and methods lessons, then the above program shouldn’t surprise you. As new struct type Circle also implements Shape interface, we can assign s a value of struct type Circle.

I guess now you can relate why the type and value of the interface are dynamic. From the slices lesson, we learned that a slice holds the reference to an array. Similarly, we can say that an interface also works in a similar way by dynamically holding a reference to the underlying type.

Can you guess what will happen to the following program?

package main

import "fmt"

type Shape interface {
	Area() float64
	Perimeter() float64
}

type Rect struct {
	width  float64
	height float64
}

func (r Rect) Area() float64 {
	return r.width * r.height
}

func main() {
	var s Shape = Rect{10, 3}
	fmt.Println(s)
}

In the above program, we removed Perimeter method. This program won’t compile and the compiler will throw an error

program.go:22: cannot use Rect literal (type Rect) as type Shape in assignment:
        Rect does not implement Shape (missing Perimeter method)

It should be obvious from the above error that in order to successfully implement an interface, you need to implement all the methods declared by the interface with exact signatures.

Empty interface

When an interface has zero methods, it is called an empty interface. This is represented by interface{}. Since the empty interface has zero methods, all types implement this interface implicitly.

Have you wondered how does the Println function from the fmt built-in package accepts the different types of value as arguments? This is possible because of an empty interface. Let’s see the signature of Println function.

func Println(a ...interface{}) (n int, err error)

As you can see, Println is a variadic function that accepts arguments of type interace``{}. Let’s understand this in little depth.

Let’s create a function explain which accepts an argument of type empty interface and explains the dynamic value & type of the interface.

package main

import "fmt"

type MyString string

type Rect struct {
	width  float64
	height float64
}

func explain(i interface{}) {
	fmt.Printf("value given to explain function is of type '%T' with value %v\n", i, i)
}

func main() {
	ms := MyString("Hello World!")
	r := Rect{5.5, 4.5}
	explain(ms)
	explain(r)
}

// value given to explain function is of type 'main.MyString' with value Hello World!
// value given to explain function is of type 'main.Rect' with value {5.5 4.5}

In the above program, we have created a custom string type MyString and a struct type Rect. Since explain function accepts an argument of the type empty interface, we can pass a variable of type MyString , Rect or others.

Since all types implement an empty interface interface{}, this is perfectly legal. Again polymorphism for the win. The parameter i of the function explain is a type of interface but its dynamic value will point to whatever value we have passed to the function as the argument.

Multiple interfaces

A type can implement multiple interfaces. Let’s dive into an example.

package main

import "fmt"

type Shape interface {
	Area() float64
}

type Object interface {
	Volume() float64
}

type Cube struct {
	side float64
}

func (c Cube) Area() float64 {
	return 6 * (c.side * c.side)
}

func (c Cube) Volume() float64 {
	return c.side * c.side * c.side
}

func main() {
	c := Cube{3}
	var s Shape = c
	var o Object = c
	fmt.Println("volume of s of interface type Shape is", s.Area())
	fmt.Println("area of o of interface type Object is", o.Volume())
}

// volume of s of interface type Shape is 54
// area of o of interface type Object is 27

In the above program, we created Shape interface with Area method and Object interface with Volume method. Since struct type Cube implements both these methods, it implements both these interfaces. Hence we can assign a value of struct type Cube to the variable of type Shape or Object.

We expect s to have a dynamic value of c and o to also have a dynamic value of c. We used Area method on s of type Shape interface because it defines Area method and Volume method on o of type Object interface because it defines Volume method. But what will happen if we used Volume method on s and Area method on o.

Let’s make these changes to the above program to see what happens.

fmt.Println("area of s of interface type Shape is", s.Volume())
fmt.Println("volume of o of interface type Object is", o.Area())

The above changes yield the following result.

program.go:31: s.Volume undefined (type Shape has no field or method Volume)
program.go:32: o.Area undefined (type Object has no field or method Area)

This program won’t compile because of the static type of s is Shape and the static type of o is Object. Since Shape does not define the Volume method and Object does not define the Area method, we get the above error.

To make it work, we need to somehow extract the dynamic value of these interfaces which is a struct of type Cube and Cube implements these methods. This can be done using type assertion.

Type assertion We can find out the underlying dynamic value of an interface using the syntax i.(Type) where i is a variable of type interface and Type is a type that implements the interface. Go will check if dynamic type of i is identical to the Type and return the dynamic value is possible.

Let’s re-write the previous example and extract the dynamic value of the interface.

package main

import "fmt"

type Shape interface {
	Area() float64
}

type Object interface {
	Volume() float64
}

type Cube struct {
	side float64
}

func (c Cube) Area() float64 {
	return 6 * (c.side * c.side)
}

func (c Cube) Volume() float64 {
	return c.side * c.side * c.side
}

func main() {
	var s Shape = Cube{3}
	c := s.(Cube)
	fmt.Println("area of c of type Cube is", c.Area())
	fmt.Println("volume of c of type Cube is", c.Volume())
}

// area of c of type Cube is 54
// volume of c of type Cube is 27

From the above program, the interface variable s of type Shape has the dynamic value of struct type Cube. Using shorthand notation, we have extracted that value with syntax s.(Cube) in variable c.

Now, we can use Area and Volume methods on c since c is a struct of type Cube and Cube implement these methods.

BEWARE! In type assertion syntax i.(Type), if i cannot hold the dynamic value of type Type because Type does not implement the interface, then the Go compiler will throw a compilation error.

impossible type assertion:
XYZ does not implement Shape (missing Area method)

But if Type implements the interface but i does not have a concrete value of Type (because it’s nil at the moment) then Go will panic in runtime.

panic: interface conversion: main.Shape is nil, not main.Cube

Luckily to avoid runtime panic, there is another variant of type assertion syntax that will fail silently.

value, ok := i.(Type)

In the above syntax, we can check using ok variable if i has concrete type Type or dynamic value of Type. If it does not, then ok will be false and value will be the zero value of the Type.

How would we know if the underlying value of an interface implements any other interfaces? This is also possible using type assertion. If Type in type assertion syntax is an interface, then Go will check if dynamic type of i implements interface Type.

package main

import "fmt"

type Shape interface {
	Area() float64
}

type Object interface {
	Volume() float64
}

type Skin interface {
	Color() float64
}

type Cube struct {
	side float64
}

func (c Cube) Area() float64 {
	return 6 * (c.side * c.side)
}

func (c Cube) Volume() float64 {
	return c.side * c.side * c.side
}

func main() {
	var s Shape = Cube{3}
	value1, ok1 := s.(Object)
	fmt.Printf("dynamic value of Shape 's' with value %v implements interface Object? %v\n", value1, ok1)
	value2, ok2 := s.(Skin)
	fmt.Printf("dynamic value of Shape 's' with value %v implements interface Skin? %v\n", value2, ok2)
}

// dynamic value of Shape 's' with value {3} implements interface Object? true
// dynamic value of Shape 's' with value <nil> implements interface Skin? false

Since the dynamic type of s is Cube and Cube implements Object interface, the first assertion succeeds. The value1 is an interface of type Object and it also points to the dynamic value of s (printed by the Printf function).

But since Cube struct does not implement the Skin interface, we got ok2 as false and value2 as nil(zero-value of the interface). If we would have used the simpler syntax of v := i.(type) syntax, then our program would have panicked in runtime with below error with an error.

panic: interface conversion: main.Cube is not  main.Skin: missing method Color

⚠️ Take note here, we need to use type assertion to get the dynamic value of an interface such that we can access properties of that dynamic value. Like for example, you can not access fields of a struct on the object of type interface, even it has dynamic value of a struct. In nutshell, accessing anything that is not represented by the interface type will cause in a runtime panic. So make sure to use type assertion when needed.

Type assertion is not only used to check if an interface has a concrete value of some given type but also to convert a given variable of an interface type to a different interface type (see the previous example or this example).

Type switch

We have seen an empty interface and its use. Let’s rethink the explain function we saw earlier. As argument type of explain function is an empty interface, we can pass any argument to it.

But if the argument is a string, we want to the explain function to print the result in the uppercase. How can we make that happen?

We can use ToUpper function from strings package but since it only accepts a string argument, we need to make sure from inside the explain function that dynamic type of empty interface i is string while doing so.

This can be done using Type switch. The syntax for type switch is similar to type assertion and it is i.(type) where i is interface and type is a fixed keyword. Using this we can get the dynamic type of the interface instead of the dynamic value.

💡 But this syntax will only work in switch statement.

Let’s see an example.

package main

import (
	"fmt"
	"strings"
)

func explain(i interface{}) {
	switch i.(type) {
	case string:
		fmt.Println("i stored string ", strings.ToUpper(i.(string)))
	case int:
		fmt.Println("i stored int", i)
	default:
		fmt.Println("i stored something else", i)
	}
}

func main() {
	explain("Hello World")
	explain(52)
	explain(true)
}

// i stored string  HELLO WORLD
// i stored int 52
// i stored something else true

In the above program, we modified the explain function to use a type switch. When an explain function is called with any type, i receives its dynamic value and dynamic type.

Using i.(type) statement inside switch, we are getting access to that dynamic type. Using cases inside the switch block, we can do conditional operations based on the dynamic type of the interface i.

In the string case, we used strings.ToUpper function to convert string to uppercase. But since it only accepts string data type, we needed to get the underlying dynamic value. Hence we used the type assertion.

Embedding interfaces

In Go, an interface cannot implement other interfaces or extend them, but we can create a new interface by merging two or more interfaces. Let’s rewrite our Shape-Cube program.

package main

import "fmt"

type Shape interface {
	Area() float64
}

type Object interface {
	Volume() float64
}

type Material interface {
	Shape
	Object
}

type Cube struct {
	side float64
}

func (c Cube) Area() float64 {
	return 6 * (c.side * c.side)
}

func (c Cube) Volume() float64 {
	return c.side * c.side * c.side
}

func main() {
	c := Cube{3}
	var m Material = c
	var s Shape = c
	var o Object = c
	fmt.Printf("dynamic type and value of interface m of static type Material is'%T' and '%v'\n", m, m)
	fmt.Printf("dynamic type and value of interface s of static type Shape is'%T' and '%v'\n", s, s)
	fmt.Printf("dynamic type and value of interface o of static type Object is'%T' and '%v'\n", o, o)
}

// dynamic type and value of interface m of static type Material is'main.Cube' and '{3}'
// dynamic type and value of interface s of static type Shape is'main.Cube' and '{3}'
// dynamic type and value of interface o of static type Object is'main.Cube' and '{3}'

In the above program, since Cube implements method Area and Volume, it implements interfaces Shape and Object. But since the interface Material is an embedded interface of these interfaces, Cube must also implement it.

This is possible because, like anonymously nested struct, all methods of nested interfaces get promoted to parent interfaces.

Pointer vs Value receiver

So far in this tutorial, we have seen methods with value receivers. Will interface be ok with method accepting pointer receiver. Let’s check it out.

package main

import "fmt"

type Shape interface {
	Area() float64
	Perimeter() float64
}

type Rect struct {
	width  float64
	height float64
}

func (r *Rect) Area() float64 {
	return r.width * r.height
}

func (r Rect) Perimeter() float64 {
	return 2 * (r.width + r.height)
}

func main() {
	r := Rect{5.0, 4.0}
	var s Shape = r
	area := s.Area()
	fmt.Println("area of rectangle is", area)
}

In the above program, the method Area belongs to *Rect type, hence its receiver will get the pointer of the variable of type Area. However, the above program will not compile and Go will throw compilation error.

program.go:27: cannot use Rect literal (type Rect) as type Shape in assignment: Rect does not implement Shape (Area method has pointer receiver)

What the hell? We can clearly see that struct type Rect is implementing all methods stated by the interface Shape, then why we are getting Rect does not implement Shape error?

If you read error carefully, it says Area method has pointer receiver. So what if the Area method has a pointer receiver?

Well, we have seen ins structs lesson that a method with pointer receiver will work on both pointer or value and if we would have used r.Area() in the above program, it would have compiled just fine.

However, with the interfaces, this story is a little different. The dynamic type of interface s is Rect and we can clearly see that Rect does not implement the Area method but *Rect does.

💡 You could ask, why r := Rect{} assignment did not fail because clearly Rect does not implement the interface Shape?

This because, a method with pointer or value receiver can be called on both value and pointer and the conversion from value to a pointer or pointer to a value suitable to be passed as a receiver for the method call is done by Go under the hood (as seen in methods lesson).

Hence, at compile time, both pointer and value can be stores in a variable of type interface. However, when calling a method on interface itself, the dynamic type is considered at runtime and dynamic value is passed as the receiver to the method.

To make this program work, instead of assigning a value of the type Rect to the interface variable s, we need to assign a pointer (of type Rect) so that pointer is passed as the receiver to Area method.

Let’s rewrite the above program with this concept.

package main

import "fmt"

type Shape interface {
	Area() float64
	Perimeter() float64
}

type Rect struct {
	width  float64
	height float64
}

func (r *Rect) Area() float64 {
	return r.width * r.height
}

func (r Rect) Perimeter() float64 {
	return 2 * (r.width + r.height)
}

func main() {
	r := Rect{5.0, 4.0}
	var s Shape = &r // assigned pointer
	area := s.Area()
	perimeter := s.Perimeter()
	fmt.Println("area of rectangle is", area)
	fmt.Println("perimeter of rectangle is", perimeter)
}

// area of rectangle is 20
// perimeter of rectangle is 18

The only change we made is in line no. 25 where instead of the value of r, we used the pointer to r so that dynamic type of s becomes *Rect and it implements the Area method.

However, s.Perimeter() call did not fail even though Perimeter is not implemented by *Area. This is one of the gotchas I struggled with.

Seems like Go is happy to pass a copy of the pointer value as the receiver to the Perimeter method perhaps because it is not a very dangerous idea, it is just a copy and nobody can mutate it accidentally.

However, I wish Go could have processed method call on interfaces similar to the structure so that we do not need to worry about pointer conversion. But it could be because of security and the interface’s ability to hold any data.

Interface comparison

Two interfaces can be compared with == and != operators. Two interfaces are always equal if the underlying dynamic values are nil, which means, two nil interfaces are always equal, hence == operation returns true.

var a, b interface{}
fmt.Println( a == b ) // true

If these interfaces are not nil, then their dynamic types (the type of their concrete values) should be the same and the concrete values should be equal.

If the dynamic types of the interface are not comparable, like for example, slice, map and function, or the concrete value of an interface is a complex data structure like slice or array that contains these uncomparable values, then == or != operations will result in a runtime panic.

If one interface is nil, then == operation will always return false.

Use of interfaces

We have learned interfaces and we saw they can take different forms. That’s the definition of polymorphism.

Interfaces are very useful in case of functions and methods where you need argument of dynamic types, like Println function which accepts any type of values.

When multiple types implement the same interface, it becomes easy to work with them. Hence whenever we can use interfaces, we should.

#golang #interfaces