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 orInterface
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
isShape
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 interfaceShape
.
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 clearlyRect
does not implement the interfaceShape
?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.