Intro
In this post we will talk about go interfaces and then show some use cases.
Definition
The definition of an interface can be put as follows
An interface type is defined as a set of method signatures. If a type has the methods that are defined in the interface, then that type it is said to implement the interface.
Okay, I admit it, this description is somehow abstract and it is okay if anyone that reads it feels confused.
An interface specifies the behavior of an object. Now if you have a type that satisfies at least the behavior of the particular interface, then it can implement that interface
Understanding interface assertion
We will try now to show how an interface works by using the empty interface as the first example and then start building on it.
Empty Interface Example:
package main
import (
"fmt"
)
type someInterface interface {
}
func main() {
var l someInterface
someString := "test"
l = someString
fmt.Println(l)
}
If we run this we get
$> go run main.go
test
Here we first defined a new interface called someInterface which does not have any fields. The interface is empty, which means any type can implement this interface.
Remember what we said above a type that has at least the expected behavior of that interface, can implement that interface. Since our interface is empty then it expects nothing from us and hence any type will satisfy it. It has no behavior and therefore anything implements it.
Adding behavior to the interface
Here we will add a behavior for our interface. Only the types that have at least that behavior will satisfy the interface.
Example:
package main
import (
"fmt"
)
type someInterface interface{
len() int
}
func main() {
var l someInterface
someString := "test"
l = someString
fmt.Println(l)
}
If we try to run the code above we get an error because the string type does not have a len() method.
go run main.go
# command-line-arguments
./main.go:13:20: cannot convert someString (type string) to type someInterface:
string does not implement someInterface (missing len method)
To satisfy this interface we can create a new type that will satisfy it.
package main
import (
"fmt"
)
type someInterface interface{
len() int
}
type SomeStruct struct {
Value string
}
func (s *SomeStruct) len() int {
return len(s.Value)
}
func (s *SomeStruct) Ping() string {
return "pong"
}
func main() {
var l someInterface
someString := "test"
someStruct := SomeStruct{
Value: someString,
}
l = &someStruct // expects a pointer
fmt.Println(l.len())
}
Running this prints
go run main.go
4
The assertion l = &someStruct did not produce an error because our new type satisfies the expected method signatures. For our example the interface expects a type to satisfy the len method which our struct does!
Note: If you noticed, our struct has a Ping() method also. The interface expects at least len(), it does not care about any extra methods that the type may have.
Interface usages
Now that we have out of our way the interface definition we can ask, “how is the interface used?”.
One type to rule them all
Okay, to understand what I say with the One type to rule them all we will descrie a common problem that engineers face when working with function arguments.
Let us assume we have the following code
package main
import (
"fmt"
)
type Dog struct {
Name string
}
func (d *Dog) getName() string {
return d.Name
}
func (d *Dog) getType() string {
return "dog"
}
type Pets struct {
All map[string]string
}
func (p *Pets) add(d Dog) {
p.All[d.getType()] = d.getType()
}
func main() {
myDog := Dog{
Name: "Jack",
}
pets := Pets{
All: make(map[string]string),
}
pets.add(myDog)
fmt.Println(pets.All)
}
In the code above we defined 2 structs with 1 method each. The struct called Pets will be used to list all the pets that someone has.
The problem with the above code is how one deals with multiple different structs. What one would have to do if a Cat struct was introduced? One solution is to create a new method addCat(c Cat), but we can quickly see that it is not an efficient way since we would have to add a new method for each different pet type that is added.
To deal with this we can instead implement an interface that expects some method signatures and use that as the argument of the Pet’s add method.
Extending with an interface
package main
import (
"fmt"
)
type Animals interface {
getName() string
getType() string
}
type Dog struct {
Name string
}
func (d *Dog) getName() string {
return d.Name
}
func (d *Dog) getType() string {
return "dog"
}
type Cat struct {
Name string
}
func (c *Cat) getName() string {
return c.Name
}
func (c *Cat) getType() string {
return "cat"
}
type Pets struct {
All map[string]string
}
func (p *Pets) add(a Animals) {
p.All[a.getName()] = a.getType()
}
func main() {
myDog := Dog{
Name: "Jack",
}
myCat := Cat{
Name: "Molly",
}
pets := Pets{
All: make(map[string]string),
}
pets.add(&myDog)
pets.add(&myCat)
fmt.Println(pets.All)
}
With the interface as the argument now it is easy to add as many different pet types as we want. All we have to do is ensure that each new type has at least the getName & getType methods.
Interface placeholder
While not at all go idiomatic, there are times that we need to deal with objects that we do not know their type. An example would be a function that it’s body is not know yet and returns a payload also not known
package main
import (
"fmt"
)
type Worker struct {
Exec func() interface{}
}
func main() {
worker := Worker{
Exec: func() interface{} {
fmt.Println("Does work")
return nil
},
}
if err := worker.Exec(); err != nil {
log.Fatal(err)
}
worker.Exec = func() interface{} {
return 1
}
fmt.Println(worker.Exec())
}
Another example would be a function that concentrates all given objects into a list
package main
import (
"fmt"
)
type Obj struct {
All []interface{}
}
func (o *Obj) add(i interface{}) {
o.All = append(o.All, i)
}
func main() {
obj := Obj{}
obj.add(1)
obj.add("a")
fmt.Println(obj.All)
}
Witht he above implementation we now have a list that can hold any type.
I hope this helped!