Golang interfaces, a pragmatic explanation for the programmer
by Emile `iMil' Heitor - 2018-12-09
I’m still in the process of learning golang the right way. Yes I already wrote some projects with the Go language (here and here), but I like to understand the real meaning of techniques when using them.
One of what is said to be the most amazing features of Go is interfaces. Here’s what the official golang docs has to say about it:
Interfaces in Go provide a way to specify the behavior of an object: if something can do this, then it can be used here. We’ve seen a couple of simple examples already; custom printers can be implemented by a String method while Fprintf can generate output to anything with a Write method. Interfaces with only one or two methods are common in Go code, and are usually given a name derived from the method, such as io.Writer for something that implements Write.
Am I just stupid? Did you actually understand this sentence at first sight? I didn’t. Maybe my english is not good enough. Let’s try another one:
An interface type is defined by a set of methods. A value of interface type can hold any value that implements those methods.
Ok… this one maybe?
An interface type is defined as a set of method signatures. A value of interface type can hold any value that implements those methods.
To be honest, I kind of got it, but why does every tutorial needs to back a hardly understandable definition with absolutely unnatural examples for us programmers? Animal
interface
with Dog
and Cat
types? Singer
? Humans
? Is that the kind of programs you write?
So I finally came up with a “real life” example that helped me out understand both interface
s capabilities:
- (the second capability will come later) An
interface
can be used for genericity, functions declared in aninterface
type can be mapped tomethods
so there’s one single name that will do the same kind of operation but using different techniques. I will use an example that we might all understand:
We programmers often have to deal with format conversions, i.e. read the content of a file and then extract its actual data. Let’s imagine you’re working on a project which chose to switch from xml
configuration files to json
format (yay!). You might want to write a generic call to handle both situations the same way, and this is where an interface
can be very convenient. Let’s have a look at what it might look like:
First let’s declare the actual interface
generic type
type Map interface {
Unmarsh() map[string][]int
}
Yeah, there’s no need to specify the func
keyword here to declare functions as there can only be this type here. So we declare the Unmarsh()
function which will -for now- return a map
suited for this kind of data: {"foo": [1, 2, 3]}
Then we declare two raw types
type Json struct {
input []byte
}
type Xml struct {
input []byte
}
Let’s create a dummy structure to receive decoded xml
, nothing related to the interface
explanation:
type Xmlout struct {
Name string `xml:"name,attr"`
Item []int `xml:"item"`
}
Now the real deal, we will write two methods one for each type, Json
and Xml
.
But wait, something won’t behave as we would like: in our project, json
and xml
Unmarshal
functions don’t act the same, json.Unmarshal()
fills a map
, while xml.Unmarshal()
fills a struct
. This means our “generic” Unmarsh()
call can’t return the same type of data.
Actually, json.Unmarshal()
knows how to fill a struct
, but for the sake of this demonstration, let’s just assume we decided that our project would be far simpler by just using a map
.
I’ll use this scenario to demonstrate the second useful usage of interface
s:
- When no methods are implemented,
interface{}
can be used as an untyped, generic data receiver. I like to see it as a form of C’svoid *
.
And here’s how it can be simply used:
type Map interface {
Unmarsh() interface{}
}
That’s right, simply replace the return type with an empty interface and the function can now return whatever type wanted.
As we’re no more bothered by the return type, we can write both Unmarsh()
methods:
func (js Json) Unmarsh() interface{} {
m := make(map[string][]int)
json.Unmarshal(js.input, &m)
return m
}
func (x Xml) Unmarsh() interface{} {
var xo Xmlout
xml.Unmarshal(x.input, &xo)
return xo
}
As you can see, those two method use different techniques to Unmarshal()
data located in the input
field of the struct
, nevertheless, we will now see how this call can be leveraged:
func main() {
var t Map
t = Json{[]byte(`{"x": [1, 2, 3]}`)}
fmt.Println(t.Unmarsh())
t = Xml{[]byte(`
<list name="x">
<item>1</item>
<item>2</item>
<item>3</item>
</list>
`)}
fmt.Println(t.Unmarsh())
}
The trick here is that the data receiver is a Map
typed (the interface
one) variable named t
, the interface
will then transparently do its magic in order to pick the right struct
and associated method based on the passed parameter type. Here I found a pretty good explanation of the implementation of such wizardry.
Witness the output of this example:
map[x:[1 2 3]]
{x [1 2 3]}
That’s right, one call to rule them all. Now obviously this is not the final result you would want to see and you’d probably transform the latter to a map pretty easily, but now hopefully you understand both usages of Go’s interface
s!