Interfaces in Go (part III)

This story introduces another set of interfaces-related topics in Golang. It explains things like method expression derived from interface types, interface type values as map keys or interfaces of embedded fields.

Method expression and interface type

Go has the concept of method expressions. It’s a way to get function from method set of type T. Such function has additional, explicit parameter of type T (source code):

type T struct {
name string
}
func (t *T) SayHi() {
fmt.Printf("Hi, my name is %s\n", t.name)
}
func main() {
t := &T{"foo"}
f := (*T).SayHi
f(t) // Hi, my name is foo
}

Language specification allows to use method expressions also for interface types:

It is legal to derive a function value from a method of an interface type. The resulting function takes an explicit receiver of that interface type.

Let’s see an example (source code):

type I interface {
M(name string)
}
type T struct {
name string
}
func (t *T) M(name string) {
t.name = name
}
func main() {
f := I.M
var i I = &T{"foo"}
f(i, "bar")
fmt.Println(i.(*T).name) // bar
}

Receiver of interface type

Go allows to define methods — functions with receiver of particular type (source code):

type T struct {
name string
}
func (t *T) SayHi() {
fmt.Printf("Hi, my name is %s\n", t.name)
}
func main() {
t1 := T{"foo"}
t1.SayHi() // Hi, my name is foo
t2 := &T{"bar"}
t2.SayHi() // Hi, my name is bar
}

Method is added to receiver’s type (in above snippet this type is *T). Such method can be called on values of type *T (or T). The takeaway from this section is the fact that interfaces cannot be used as receiver’s type (source code):

type I interface {}
func (I) M() {}

It throws a compile-time error invalid receiver type I (I is an interface type). More on methods in two stories introducing this topic thoroughly — part I and part II.

“Inheritance” of interfaces

Interface is satisfied by struct type even if implemented method(s) is promoted so it comes from embedded (anonymous) field (source code):

type T1 struct {
field1 string
}
func (t *T1) M() {
t.field1 = t.field1 + t.field1
}
type T2 struct {
field2 string
T1
}
type I interface {
M()
}
func main() {
var i I = &T2{"foo", T1{field1: "bar"}}
i.M()
fmt.Println(i.(*T2).field1) // barbar
}

In this case type *T2 implements interface I. Method M is implemented by type *T1 which is an embedded field of type T2. More on promoted fields and methods in older post.

Interface type value as map key or element

Map (hash table under the hood as of Go ≤ 1.8) is a data structure which for defined keys holds some values (source code):

counters := make(map[string]int64)
counters["foo"] = 1
counters["bar"] += 2
fmt.Println(counters) // map[foo:1 bar:2]
delete(counters, "bar")
fmt.Println(counters) // map[foo:1]
fmt.Println(counters["bar"]) // 0
if _, ok := counters["bar"]; !ok {
fmt.Println("'bar' not found")
}
counters["bar"] = 2
for key, value := range counters {
fmt.Printf("%s: %v\n", key, value) // order is randomized!
}

Interface type values can be used as both keys and values inside maps (source code):

type T1 struct {
name string
}
func (t T1) M() {}
type T2 struct {
name string
}
func (t T2) M() {}
type I interface {
M()
}
func main() {
m := make(map[I]int)
var i1 I = T1{"foo"}
var i2 I = T2{"bar"}
m[i1] = 1
m[i2] = 2
fmt.Println(m) // map[{foo}:1 {bar}:2]
}

Omnipresent interfaces

error

The built-in error in Go is an interface:

type error interface {
Error() string
}

Every type implementing Error method not taking any parameters and returning string value as a result, satisfies this interface (source code):

import "fmt"
type MyError struct {
description string
}
func (err MyError) Error() string {
return fmt.Sprintf("error: %s", err.description)
}
func f() error {
return MyError{"foo"}
}
func main() {
fmt.Printf("%v\n", f()) // error: foo
}

io.Writer

Interface io.Writer has only one method — Write:

Write(p []byte) (n int, err error)

If anything abnormal will happen then error won’t be nil. Interface error is the same interface described in preceding section. Writer interface is used throughout the standard library for things like MultiWriter, TeeReader, net/http and many more.