Go Pointers: Why I Use Interfaces (in Go)

Emphasis on I and in Go.

If you’ve been coding for a while, I probably don’t need to explain all the obvious benefits of interfaces, but I’m going to take just a moment or two to level set before I dive into the more idiosyncratic reasons I use interfaces in Go.

Skip ahead if you’re confidant in your understanding of interfaces in general.

Level setting

There are a lot of neat things that you can achieve using interfaces that you could not otherwise. For instance, you can create multiple components that calling code can interact with in a uniform manner, even if the underlying implementation of those components varies wildly. This creates the possibility of swapping components that implement a common interface with one another at compile time or even dynamically at runtime.

A convenient real world example is that of Go’s io.Reader interface. All implementations of the io.Reader interface support a Read(p []byte) (n int, err error) function. Consumers coding to the io.Reader interface do not need to know where the bytes obtained by calling that function come from.

All of this is common sense for anyone who has been programming for a while.

Enter Go…

Go doesn’t have constructors!

For example, imagine that all “widgets” must have an immutable, system-assigned identifier. In Java, for instance, this is easy to implement:

There is no way to instantiate a new Widget without its initialization logic being executed. Brilliant!

Go doesn’t have this feature. :(

In go, instances of user-defined types are instantiated directly.

Given the following:

A widget might be instantiated and used like so:

If you run this example, the (perhaps) unsurprising result is that the identifier that is printed is the empty string — because it was never initialized and an empty string is the “zero value” for a string.

We can add a “constructor-like” function to our widgets package to handle initialization:

And we can easily amend our main function to use this new constructor-like function:

Executing this program, we find the desired result.

But we still have a HUGE problem! Absolutely nothing forces a user of our widgets package to instantiate instances of the Widget type using our constructor-like function.

Going private

Our main program remains unchanged and should still work as-is. This is a step closer to what we want, but we’ve committed a non-obvious cardinal sin in the process. The constructor-like NewWidget() function returns an instance of an unexported type. While the compiler does not balk at this, this is still considered a bad practice and warrants some explanation.

In Go, packages are the fundamental unit of reuse. (Contrast this to other languages where a class might be the fundamental unit of reuse.) Anything unexported is, as previously noted, essentially “package private.” i.e. Anything unexported is an implementation detail of that package and should be of no consequence to other packages that consume ours. Owing to this, Go’s documentation-generating tool godoc does not generate documentation for unexported functions, types, etc.

By returning an instance of the unexported widget type in our constructor-like function, we inadvertently created a “dead end” in the documentation. A developer invoking our constructor-like function may obtain an instance of a widget, but will never find any documentation covering the existence or proper use of the ID() accessor function. The Go community takes documentation very seriously, so this is frowned upon.

Interfaces to the rescue!

By creating an exported interface that our widget type will implement, our constructor-like function can return something that is exported and documented instead of something that isn't, while the underlying implementation of the interface remains unexported and cannot be instantiated directly by consumers.

Wrap up

In my next post, I’ll cover a scenario that is nearly the inverse of this one — a case where you might have used an interface in any other language, but can get by without using one in Go!

Kent is a senior engineer on the Azure Cloud Native Computing team at Microsoft, working primarily with Kubernetes and other open source projects.