Blog.

Go interfaces and type assertions

MF

Marco Franssen /

10 min read1852 words

Cover Image for Go interfaces and type assertions

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.

main.go
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())
  }
}

playground

When running this you will see following output.

terminal
$ 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.

main.go
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)
}

playground

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.

main_test.go
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)
  }
}

playground

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.

main_test.go
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)
  }
}

playground

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.

main.go
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
}

playground

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.

You have disabled cookies. To leave me a comment please allow cookies at functionality level.

More Stories

Cover Image for Docker tips and tricks for your Go projects

Docker tips and tricks for your Go projects

MF

Marco Franssen /

In this blogpost I would like to show you some basic Docker setup I have been using so far in my Go projects. We will be looking at multi-stage Docker builds and how to utilize docker-compose. In a typical project setup in Go you would most probably start with a file main.go. In addition to that I usually add a Dockerfile for building a Docker image and a docker-compose file to easily spin up my dependencies like databases and queues. To start we create a new folder to work in and initiales th…

Cover Image for Go webserver with graceful shutdown

Go webserver with graceful shutdown

MF

Marco Franssen /

In this blogpost I want to show you how you can make a http webserver in Go with gracefull shutdown. Using this approach you allow the server to clean up some resources before it actually shuts down. Think about finishing a database transaction or some other long operation. We will be using the things we learned in my blogpost on concurency. So expect to see channels and go routines as part of the solution. When I create new http servers I usually start with an commandline flag to provide the p…

Cover Image for Test and benchmark your code in go

Test and benchmark your code in go

MF

Marco Franssen /

When I started writing my first programs in Go, I noticed the tooling ships out of the box with test and benchmark features. You simply follow some naming conventions when it comes to file names. You import a reference to the "testing" package which is kind of part of the language. Aaaand… Ready set, and of you Go with writing some tests and benchmarks in Go. In my earlier blog post I briefly touched writing a test in Go already. I recommend reading this blogpost whenever you are a real newby wi…

Cover Image for Concurrency in Go

Concurrency in Go

MF

Marco Franssen /

A reason to choose Go over other programming languages could be to have the need for building software which requires concurrency. Go is built with concurrency in mind. You can achieve concurrency in Go by using Go routines. A Go routine is a lightweidght thread to explain this in easy words for the people with c# and Java backgrounds. Please experienced Gophers don't take my words litterly as I do know a Go routine shouldn't be compared to threads like this, but at least it is the easiest for m…