I was peacefully trying to finish a post about user namespaces when a friend came home and arrogantly told me I do know nothing about Golang interfaces. So, here we are. Context: Ubuntu 18.04, Python 3.7 and go version go1.11 linux/amd64.



It's about defining & implementing behaviours

An interface is a description of the actions that an item can do. When you flip a light switch, the light goes on, or off, you don't care how things are implemented, you just care that it goes on, or off.



Python's repr function

You may have heard about the Python repr function, taking any valid Python object, or values of any type, and returning a string containing a printable representation of the object:

>>> type(1)
<class 'int'>
>>> repr(1)
'1'
>>>
>>> type(1.1)
<class 'float'>
>>> repr(1.1)
'1.1'
>>>
>>> type('foo')
<class 'str'>
>>> repr('foo')
"'foo'"
>>>


Another example:

>>> class A:
...   name = 'nsukami'
...
>>> p = A()
>>> # A doesn't implement __repr__ method, default representation returned
>>> repr(p)
'<__main__.A object at 0x7f4f9dece898>'
>>>
>>>
>>> class B:
...   name = 'nsukami'
...   def __repr__(self):
...     return f"My name is {self.name}"
...
>>> # B override or B implement __repr__ method, custom representation returned
>>> repr(B())
'My name is nsukami'
>>>



The __repr__ magic method

Yes, object.__repr__ method and all the other magic or special methods are Python's approach for allowing classes to define their own behavior.

When we call the repr function, what is happening behind the scenes is this:

  • the repr method takes values of any type as argument
  • thanks to mro, the order in which __repr__ is overridden is known.
  • if an implementation of __repr__ is found, then it will be applied



Golang interfaces

Interface types express generalizations or abstractions about the behaviours of other types. Interfaces let us write functions that are more flexible and adaptable. Interfaces let us achieve polymorphism. Example:

package main

import "fmt"

// To satisfy I, you need to implement Foo behaviour
type I interface {
  Foo()
}

type A struct {}

// A is implicitly satisfying I w/o changing the definition of A
func (p A) Foo() { fmt.Println("foo") }

type B struct {}

// B is implicitly satisfying I or wa can say: B "is a" I
func (p B) Foo() { fmt.Println("bar") }

// F will take any argument with Foo() behaviour
// or F will take any argument satisfying I
func F(i I) {
  i.Foo()
}

func main() {

  a := A{}
  b := B{}

  l := [...]I{a, b}
  for n, _ := range(l) {
    F(l[n]) // appears as I type, but behaviour changes depending on current instance
  }
}


Now, let's do with Go, what we've done with Python:

package main

import (
    "fmt"
)

type A struct{
    name string
}

type B struct{
    name string
}

// B struct is now implementing the Stringer interface
func (b B) String() string {
    return fmt.Sprintf("My name is %s", b.name)
}


func main() {
    a := A{name: "Nsukami"}
    b := B{name: "Nsukami"}
    fmt.Println(a)
    fmt.Println(b)
}



The fmt.Println function

The fmt.Println function, is not returning a string like the Python's repr function, but that's not the point. The output of the fmt.Println function can be customized if the passed value implement the Stringer interface. With Golang, when we call fmt.Println, what's happening behind the scenes is :

  • fmt.Println takes an arbitrary number of empty interfaces as arguments.
  • thanks to the way interface values are stored, all the implemented interfaces are known.
  • if an implementation of Stringer interface is found, then it will be applied.



The empty interface?

Yes. An empty interface may hold values of any type. Example:

package main

import (
    "fmt"
    //"reflect"
)

type A struct{
    name string
}

type B struct{
    name string
}

// B struct is now implementing the Stringer interface
func (b B) String() string {
    return fmt.Sprintf("My name is %s", b.name)
}


// f takes an empty interface as argument
// f can take as argument, values of any type
func f(i interface{}) {
    // nevertheless, we perfectly know the type that was passed to us
    // and we can retrieve the right implementation of the Stringer interface

    // fmt.Print("Dynamic type: ", reflect.TypeOf(i).String(), ", Concrete value: ", i, "\n")
    fmt.Printf("Dynamic type: %T, Concrete value: %v\n", i, i)

}

func main() {
    f(B{name: "foo"})
    f(A{name: "foo"})
    f(1)
    f(1.1)
    f("nsukami")
}



the way interface value are stored?

The best way to understand how interface values are stored, is to read the following awesome article, really.



Type assertion?

A type assertion is an operation applied to an interface value. A type assertion checks that the dynamic type of its operand matches the asserted type. Simply said: x.(T) asserts that x is not nil and that the concrete value stored in x is of type T. Example:

package main

import (
    "fmt"
)


type B struct{
    name string
}

func (b B) String() string {
    return fmt.Sprintf("My name is %s", b.name)
}

func f(i interface{}) {
    if _, ok := i.(B); ok {
        // if i is a B, do something
        fmt.Println("i is B, let's do something")
    }else{
        fmt.Println("is is not a B")
    }

}

func main() {
    var i interface{} = "baz"

    // is i a string?
    s := i.(string)

    // if i is a string, no panic will occur
    fmt.Println(s)

    // if you uncomment the 2 following lines, the program will panic
    // because i is not a float 64
    // r := i.(float64)
    // fmt.Println(r)

    // to handle panic gracefully, retrieve the 2nd returned value of the type assertion
    r, ok := i.(float64)
    fmt.Println(r, ok)

    // type assertion inside if conditions
    f(B{name: "foo"})
    f("nsukami")

}




Recap?

  • You achieve polymorphism in Go with interfaces, in Python, with inheritance, mixins, and ABC.
  • Interfaces in Go are a little bit like Python magic methods, they help you implement behaviours.
  • Type assertions in Go are a little bit like Python's built-in function isinstance.
  • In Go, you can define your own interfaces. In Python, you cannot define your own magic methods.
  • In Go, every type implements the empty interface. In Python3, all objects are instances of object.



More on the topic:

I hope I was at least able to bring you another perspective on this topic, really. May I please, recommend the following links?


** YaitGi: Yet another introduction to Golang interfaces.



Not so unexpected Quote:

"Behaviour is a mirror in which every one displays his own image." ― Johann Wolfgang von Goethe