A Closer Look at Go Interfaces

Michael Sauter
Tweet

Ever heard of “The best interface is no interface”? While that may be true for product design, it certainly isn’t when it comes to programming Go. On the contrary, it’s probably better to follow the principle “err on the side of too many interfaces”. Why? In this article, we’ll look at what interfaces are, how they are implemented in Go and why they are so useful. Let’s dive right in!

What are interfaces?

Generally speaking, an interface defines the behavior of an object and therefore how this object can be used by other parts of the system. The interface describes what can be done by the object, but not how the object actually caries out the instructions. As a consequence, the implementation of the object can be exchanged without breaking functionality for the consumer.

In languages like PHP or Java, interfaces specify which methods a class must implement, and in turn each class declares which interfaces it implements. Many classes can implement the same interface, and the system can then operate on the interfaces instead of the implementation.

Interfaces in Go

In Go, interfaces have the same purpose, but there are a few differences to traditional languages which make interfaces much more useful and versatile. So, what are interfaces in Go? Firstly, an interface in Go is simply defined as a set of methods. For example, the Reader interface from the standard library looks like this:

type Reader interface {
    Read(p []byte) (n int, err error)
}

It specifies one method, Read, which has one argument of type []byte and returns two named parameters (of int and error type). An interface containing just one method might look unusual to you at first, but it’s actually not uncommon in Go. Go is full of beautiful, small, composable pieces – we’ll come back to that later. For now, let’s take another look at the code. We notice that the name of the interface is just the method name Read plus an -er at the end, which is a convention in Go. For example, we also have a Writer and a Printer interface, which, as you’d expect, consist of a Write and a Print method, respectively.

The second important bit to know about interfaces in Go is that they introduce a new type. In the example above, we declare the new type Reader, which is of the interface type. The value of an interface type can be anything that satisfies the interface.

So how do types implement an interface? Traditionally, types would declare their intent to implement an interface, for example by using the keyword implements in PHP or Java. In Go, interfaces are satisfied implicitly. A type implements an interface if it implements all methods of the set. So, anything is a Reader that has a Read(p []byte) (n int, err error) method. For example, the Buffer type from the standard library can be used as a Reader because it implements the Read method.

Still with me? Good! Let’s see how this all makes interfaces in Go so powerful and what they can be used for.

The empty interface

I mentioned that interfaces are sets of methods. Remember your Maths classes from way back? You’ve probably learned that the smallest set is the empty set. So, in Go, the smallest interface is the empty interface, which is noted as interface{}. This interface is implemented by every type in Go, because every type has at least no methods (and interfaces are satisfied implicitly!). While this might be logical, what should an interface with no methods be good for? Well, since an interface also introduces a new type, we now have a type that can be used in places where we handle values of unknown type. It’s somewhat similar to “duck typing” in dynamic languages. It’s probably a good idea to avoid the empty interface as much as possible in Go, but it’s a great thing to have available when you need it. For example, the Print function of the fmt package takes any number of values of type interface{}:

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

This makes perfect sense since you might want to print a string or an int, but you don’t want a different Print function for every type. Internally, Print uses type assertions to convert every argument to the correct type, and then print it accordingly.

Composite interfaces

Up for some more set theory? Remember that a set can also contain other sets. The outer set then consists of all the elements of the inner sets. And sure enough, this is possible in Go, too. There is even a prominent example in the standard library: the ReadWriter interface. The ReadWriter is the set of the Reader interface (which we saw earlier) and the Writer interface. It is declared like this:

type ReadWriter interface {
    Reader
    Writer
}

This means that anything that implements Reader and Writer at the same time will also implement ReadWriter (automatically!). Remember I was talking about Go’s small beautiful pieces? This is one of them.

What can implement interfaces?

So far, we know that anything that implements the method set of the interface will satisfy the interface. Now, in contrast to e.g. PHP or Java, almost anything in Go can have methods attached. That means in turn, that almost anything can satisfy an interface. For example:

type Counter int
func (counter * Counter) Read(p []byte) (n int, err error) {
    ...
}

We’re defining a new type Counter, which is of type int. Then, we attach a Read method and that’s all we need to use our Counter type anywhere a Reader is used (although I have no idea what a Counter reads …)

Making use of the standard library

Say you now want to print the Counter. Of course you could write a method that does it for you, but it’s more convenient to look at the fmt package and see what types are expected by the functions concerned with printing, e.g. Print. As it turns out, it can print values of the Stringer interface type. By convention, Stringer specifies one method, named String which just returns a string. So, if you add a method String to your type, it will satisfy the Stringer interface. You can then pass it to fmt.Print and it will print out whatever you return in the String method.

Actually, you could have passed the Counter as-is to fmt.Print because it knows how to print ints, but another benefit of using a String method is that it allows you to add more text when printing, for example:

func (counter *Counter) String() string {
    return "My counter value: " + strconv.Itoa(counter)
}

There are many ways to make use of the standard library by implementing the right interfaces – and often it requires just one method.

Conclusion

We have seen that Go interfaces are a very powerful concept. At the same time they feel much more lightweight than in other languages, mostly because they are implicitly satisfied. As a consequence, interfaces build one of the central pieces when designing a system in Go. Err on the side of too many of them!

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • http://a55951234.twgg.org/ CodinCat

    Great article, hope there are more tutorials about Go.

  • karthik nayak

    Brilliant :D