Go interfaces and type assertions

Marco Franssen /
10 min read • 1852 words

In this blog I would like to zoom in on Interfaces and type assertions in Go. Compared to language like c#
and Java
implementing interfaces works slightly different. In the remainder of this blog I want to give you a bit of theory and practical usecases. In case this is your first time working with Go you might want to check out this blog which shows you how to setup your development environment including a small hello world.
The empty interface (interface{}
) is an interface which defines zero methods. An empty interface may hold values of any type. Any type is at its basics a type without methods. It is used by code that handles values of unknown type. E.g. fmt.Print
takes any number of arguments of type interface{}
. This is the least valuebale interface in terms of reusing logic, but could come very handy in some situation where you simply do not know the type up front. It comes with a cost of not having type safety at the places you use this empty interface.
Now lets have a look at how we can define our own interface. A interface in Go is defined as a set of method signatures. In Go interfaces are implicit. So there is no need to define on a given type that it implements a certain interface. The advantage of this is that the definition of an interface is decoupled from its implementation which could then appear in any package without prearrangement. Later on we will zoom in on this decoupling further.
First lets have a look at an example of the definition of an interface and how a given value type can implement an interface.
package main
import "fmt"
type Animal interface {
Call() string
}
type Cow struct {
name string
}
type Dog struct {
name string
}
func (c *Cow) Call() string {
return fmt.Sprintf("Mooohoooo %s Booo!", c.name)
}
func (d *Dog) Call() string {
return fmt.Sprintf("WooofWaf %s Wuf!", d.name)
}
func main() {
var animals []Animal = []Animal{
&Cow{"Betsy"},
&Dog{"Roover"},
&Cow{"Bull"},
&Dog{"Fifi"},
}
for _, animal := range animals {
fmt.Println(animal.Call())
}
}
When running this you will see following output.
$ go run main.go
Mooohoooo Betsy Booo!
WooofWaf Roover Wuf!
Mooohoooo Bull Booo!
WooofWaf Fifi Wuf!
At line 5 we defined an interface with a single method Call() string
. This interface is implicitly implemented on line 17 and 21. The syntax we see over there is what we call a receiver method. In this case it is a receiver which receives a pointer to Cow (*Cow
) and a pointer to Dog (*Dog
). These receivers you could compare to Object.prototype.myMethod
in Javascript
to build a small bridge to a language you might have more experience with. As you can see there is no such thing like we have to do in c#
or Java
where you have to define on the class which interface you are implementing. Due to the fact both our Cow and our Dog implement the Animal interface we now have the ability to create a slice of Animals. Then we simply loop on the slice of animals to have them all call out their own names.
Type assertions
With type assertions we can get access to an interface underlying value. c := a.(Cow)
will assign the underlying value Cow of the animal interface to the variable c. If a is not a Cow this will cause a panic. So in general we will not user this syntax to prevent panics. Instead we will use the following syntax which tests if the interface value holds a certain type.
package main
import "fmt"
func main() {
var i interface{} = "Howdy fellow Gophers"
fmt.Println("Type asserting test string:")
s, ok := i.(string)
fmt.Println(s, ok)
fmt.Println()
fmt.Println("Type asserting test float64:")
f, ok := i.(float64)
fmt.Println(f, ok)
fmt.Println()
fmt.Println("Type asserting byte slice:")
b := i.([]byte)
fmt.Println(b)
}
In above example I have been using the empty interface. With above example you will notice that ok will hold a boolean value which you can use in an if statement for example. s
, f
and b
would hold the underlying value of the underlying type, if the assertion succeeds. Also notice the last assertion we are not doing a test, which causes a panic. In case you want to know more on how to handle panic, you can have a look on this blogpost which has some examples on recovering from a panic.
As a handson example you could try to apply type assertions on the first example in this blogpost. Try to get access to the underlying value of the animals in the for loop to print the underlying value.
Example using type assertions for errors
So now we know a bit on how to work with interfaces in Go I would like to highlight a nice usecase to define your own Error structs and why to use them. In Go error
is also an interface. You will see many packages have methods with signature like this func SomeMethod() error
or func SomeMethod() (string, error)
.
In general we could implement such a method as following and then start testing for the error message string.
package main
import (
"fmt"
"io/ioutil"
"net/http"
"testing"
)
func GetData(url string) (string, error) {
res, err := http.Get(url)
if err != nil {
return "", fmt.Errorf("could not fetch from %s, %v", url, err)
}
if res.StatusCode != http.StatusOK {
return "", fmt.Errorf("did not get 200 response from %s, %v", url, res.StatusCode)
}
defer res.Body.Close()
body, _ := ioutil.ReadAll(res.Body) // ignore the error here for brevity
return string(body), nil
}
func TestGetData(t *testing.T) {
body, err := GetData("htp://faulty.url")
if err == nil {
t.Errorf("Expected an error, got '%v'", err)
}
expectedErr := fmt.Sprintf("could not fetch from %s, %v", "htp://faulty.url", "Get htp://faulty.url: unsupported protocol scheme \"htp\"")
if err.Error() != expectedErr {
t.Errorf("Expected error\n%s\nbut got\n%v", expectedErr, err)
}
if body != "" {
t.Errorf("Expected no body, got'%v'", body)
}
}
As you can see the tests like this would be pretty clumsy and take pretty much effort to implement these kind of tests. E.g. In the tests I don't want to really care for the exact error message. I rather just would like to test if I got an error of a certain type. The error interface in Go looks as following.
type error interface {
Error() string
}
So in order for us to define our own error
types we just have to implement the Error() string
function on our struct. So lets have a look at a rewrite of above example where we use our own error
type and rewrite the test to use type assertions.
package main
import (
"fmt"
"io/ioutil"
"net/http"
"testing"
)
type FetchError struct {
url string
err error
}
func NewFetchError(u string, e error) FetchError {
return FetchError{u, e}
}
func (e FetchError) Error() string {
return fmt.Sprintf("could not fetch from %s, %v", e.url, e.err)
}
type HttpError struct {
url string
code int
}
func NewHttpError(u string, c int) HttpError {
return HttpError{u, c}
}
func (e HttpError) Error() string {
return fmt.Sprintf("did not get 200 response from %s, %v", e.url, e.code)
}
func GetData(url string) (string, error) {
res, err := http.Get(url)
if err != nil {
return "", NewFetchError(url, err)
}
if res.StatusCode != http.StatusOK {
return "", NewHttpError(url, res.StatusCode)
}
defer res.Body.Close()
body, _ := ioutil.ReadAll(res.Body) // ignore the error here for brevity
return string(body), nil
}
func TestGetData(t *testing.T) {
body, err := GetData("htp://faulty.url")
if err == nil {
t.Errorf("Expected an error, got '%v'", err)
}
fetchError, ok := err.(FetchError)
if !ok {
t.Errorf("Expected fetchError but got %v", fetchError)
}
if body != "" {
t.Errorf("Expected no body, got '%v'", body)
}
}
As you can see this makes your tests a lot more clean. On line 59 you see we are using a type assertion to check if we are getting a FetchError
. You could do similar assertions in your production code to handle each type of error differently. Imagine you would have to make those kind of decisions based on the error string all over the place. That would become a disaster.
In case you want to learn more about testing your Go code you can also have a look at following blog post, which zooms in a bit more on testing and benchmarking your code.
Bonus
Type assertions solution for the animals assignment I gave you earlier.
package main
import "fmt"
type Animal interface {
Call() string
}
type Cow struct {
name string
}
type Dog struct {
name string
}
func (c *Cow) Call() string {
return fmt.Sprintf("Mooohoooo %s Booo!", c.name)
}
func (d *Dog) Call() string {
return fmt.Sprintf("WooofWaf %s Wuf!", d.name)
}
func main() {
var animals []Animal = []Animal{
&Cow{"Betsy"},
&Dog{"Roover"},
&Cow{"Bull"},
&Dog{"Fifi"},
}
for _, animal := range animals {
fmt.Println(getSpecies(animal))
fmt.Println(animal.Call())
fmt.Println()
}
}
func getSpecies(i interface{}) string {
var m string
switch a := i.(type) {
case Cow:
case *Cow:
m = fmt.Sprintf("I'm a Cow, %#v", a)
break
case Dog:
case *Dog:
m = fmt.Sprintf("I'm a Dog, %#v", a)
break
default:
m = "Don't know what species this is"
}
return m
}
Using the switch approach we could extend the behavior of our code. The variable a
will be of the matched type as you can see when running the code in the playground. So imagine a Cow would have additional methods you could do something with those methods in the switch case for Cow. Also notice on the getSpecies method I have used the empty interface (interface{}
). In this case we also could have used our Animal
interface ofcourse as we only use it on our animals, but imagine we would pass something else then animal this function would still work. In general I like to avoid the empty interface as it doesn't allow for compile time checks. However there will be usecases where you will need them, as there are no generics in Go yet. For Go 2 there are plans for generics as well contracts
that would add compile time checks for usecases we now need to implement using interfaces and type assertions.
Thanks for reading. Hopefully this clarified a bit on the awesomeness of interfaces in Go which I really like as they are more loosely coupled then in other programming languages. I would appreciate it if you could share my blog on social media. Comments are also very welcome.