[Go] Interface

Interfaces

An interface type is defined as a set of method signatures. A value of interface type can hold any value that implements those methods.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
type MyFloat float64
type Abser interface {
Abs() float64
}
type Vertex struct {
X, Y float64
}

func (f MyFloat) Abs() float64 {
if f < 0 {
return float64(-f)
}
return float64(f)
}

func (v *Vertex) Abs() float64 {
return math.Sqrt(v.X*V.X + v.Y*v.Y)
}

func main() {
var ab Abser
f := MyFloat(-math.Sqrt2)
v := Vertex{3, 4}

ab = f // ab MyFloat implements Abser
ab = &v // ab *Vertex implements Abser
ab = v // ab is of type Vertex, but doesn't implement Abser
}

Vertex (the value type) doesn’t implement Abser because the Abs method is defined only on *Vertex (the pointer type).

Interfaces are implemented implicitly

A type implements an interface by implementing its methods. There is no explicit declaration of intent, no “implements” keyword.

Implicit interfaces decouple the definition of an interface from its implementation, which could then appear in any package without prearrangement.

Under the hood, interface values can be thought of as a tuple of a value and a concrete type:

(value, type)

An interface value holds a value of a specific underlying concrete type.

Calling a method on an interface value executes the method of the same name on its underlying type.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
type I interface {
M()
}

type T struct {
S string
}

type F float64

func (t *T) M() {
if t == nil {
fmt.Println("<nil>")
return
}
fmt.Println(t.S)
}

func main() {
var i I = &T{"Hello"}
describe(i) // (&{"Hello"}, *main.T)
i.M() // Hello

i = F(math.Pi)
describe(i) // (3.1415..., main.F)
i.M() // 3.1415... ?

var t *T
var j I = t
describe(j) // (<nil>, *main.T)
j.M() // <nil>
}

func describe(i I) {
fmt.Printf("(%v, %T)\n", i, i)
}

Interface values with nil values

If the concrete value inside the interface itself is nil, the method will be called with a nil receiver.

In some languages this would trigger a null pointer exception, but in Go it is common to write methods that gracefully handle being called with a nil receiver (as with the method M in this example.)

Note that an interface value that holds a nil concrete value is itself non-nil.

Empty interface

The interface type that specifies zero methods is known as the empty interface:

interface{}

An empty interface may hold values of any type. (Every type implements at least zero methods.)

Empty interfaces are used by code that handles values of unknown type. For example, fmt.Print takes any number of arguments of type interface{}.

1
2
3
4
5
6
7
8
9
10
func main() {
var i interface{}
describe(i) // (<nil>, <nil>)

i = 42
describe(i) // (42, int)

i = "hello"
describe(i) // ("hello", string)
}

Type Assertions

A type assertion provides access to an interface value’s underlying concrete value.

t := i.(T)

This statement asserts that the interface value i holds the concrete type T and assigns the underlying T value to the variable t.

If i does not hold a T, the statement will trigger a panic.

To test whether an interface value holds a specific type, a type assertion can return two values: the underlying value and a boolean value that reports whether the assertion succeeded.

t, ok := i.(T)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func main() {
var i interface{} = "hello"

s := i.(string)
fmt.Println(s) // hello

s, ok := i.(string)
fmt.Println(s, ok) // hello true

f, ok := i.(float)
fmt.Println(f, ok) // 0 false

f = i.(float64) // panic
fmt.Println(f)
}

Type switches

A type switch is a construct that permits several type assertions in series.

A type switch is like a regular switch statement, but the cases in a type switch specify types (not values), and those values are compared against the type of the value held by the given interface value.

This switch statement tests whether the interface value i holds a value of type T or S. In each of the T and S cases, the variable v will be of type T or S respectively and hold the value held by i. In the default case (where there is no match), the variable v is of the same interface type and value as i.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func do(i interface{}) {
switch v := i. (type) {
case int:
fmt.Printf("Twice %v is %v\n", v, v*2)
case string;
fmt.Printf("%q is %v bytes long\n", v, len(v))
default:
fmt.Printf("I don't know about this type %T!\n", v)
}
}

func main() {
do(21)
do("hello")
do(true)
}

Stringer interface

A Stringer is a type that can describe itself as a string.

1
2
3
type Stringer interface {
String() string
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
type Person struct {
Name string
Age int
}

func (p Person) String() string {
return fmt.Sprintf("%v (%v years)", p.Name, p.Age)
}

func main() {
a := Person{"Arthur Dent", 42}
z := Person{"Zaphod Beeblebrox", 9001}
fmt.Println(a, z)
}