Interfaces in Go (part II)

Type assertion & type switch

Michał Łowicki
golangspec
Published in
10 min readFeb 20, 2017

--

There are times when value needs to be converted to a different type. Conversion is checked at compilation-time and the whole mechanism has been covered in older post. In short it looks like this (source code):

type T1 struct {
name string
}
type T2 struct {
name string
}
func main() {
vs := []interface{}{T2(T1{"foo"}), string(322), []byte("abł")}
for _, v := range vs {
fmt.Printf("%v %T\n", v, v)
}
}

output:

{foo} main.T2
ł string
[97 98 197 130] []uint8

Golang has assignability rules which in some cases allow to assign to variable a value of a different type (source code):

type T struct {
name string
}
func main() {
v1 := struct{ name string }{"foo"}
fmt.Printf("%T\n", v1) // struct { name string }
var v2 T
v2 = v1
fmt.Printf("%T\n", v2) // main.T
}

This story will focus on conversions when interface types are involved. Additionally new constructs will be introduced — type assertion and type switch.

Let’s suppose we’ve two interface type variables and we want to assign one to another (source code):

type I1 interface {
M1()
}
type I2 interface {
M1()
}
type T struct{}func (T) M1() {}func main() {
var v1 I1 = T{}
var v2 I2 = v1
_ = v2
}

This was easy as the program works completely fine. The 3rd assignability case applies here:

T is an interface type and x implements T.

It’s because v1’s type implements I2 interface. It doesn’t matter how these types are structured (source code):

type I1 interface {
M1()
M2()
}
type I2 interface {
M1()
I3
}
type I3 interface {
M2()
}
type T struct{}func (T) M1() {}
func (T) M2() {}
func main() {
var v1 I1 = T{}
var v2 I2 = v1
_ = v2
}

Even if I2 has other interface embedded but I1 does not then these interfaces still implement each other. Order of methods also doesn’t matter. It’s worth to remember that methods sets don’t have to be equal (source code):

type I1 interface {
M1()
M2()
}
type I2 interface {
M1()
}
type T struct{}func (T) M1() {}
func (T) M2() {}
func main() {
var v1 I1 = T{}
var v2 I2 = v1
_ = v2
}

Such code works just fine also because of 3rd case of assignability. Value of type I2 implements I1 since its method set is a subset of methods from I1. If this is not the case then compiler will react accordingly (source code):

type I1 interface {
M1()
}
type I2 interface {
M1()
M2()
}
type T struct{}func (T) M1() {}func main() {
var v1 I1 = T{}
var v2 I2 = v1
_ = v2
}

Above code doesn’t compile because of an error:

main.go:18: cannot use v1 (type I1) as type I2 in assignment:
I1 does not implement I2 (missing M2 method)

So for we’ve seen how cases where two interface types are involved. 3rd case of assignability listed before applies also when right-side value is of concrete type (non-interface type) and implements an interface (source code):

type I1 interface {
M1()
}
type T struct{}func (T) M1() {}func main() {
var v1 I1 = T{}
_ = v1
}

How it works though when interface type value needs to be assigned to variable of concrete type? (source c0de)

type I1 interface {
M1()
}
type T struct{}func (T) M1() {}func main() {
var v1 I1 = T{}
var v2 T = v1
_ = v2
}

This doesn’t work and throws an error cannot use v1 (type I1) as type T in assignment: need type assertion. This is where type assertion steps in…

Conversion can be done only if Go compiler is able to check its correctness. Scenarios where it isn’t verifiable at compile-time are as follows:

  1. interface type → concrete type (source code)
type I interface {
M()
}
type T struct {}
func (T) M() {}
func main() {
var v I = T{}
fmt.Println(T(v))
}

It gives a compilation error cannot convert v(type I) to type T: need type assertion. It’s because the compiler doesn’t know if such implicit conversion is valid since any value implementing interface I can be assigned to variable v.

2. interface type → interface type, where the method set of the right side isn’t a subset of the method set from the type on the left (source code)

type I1 interface {
M()
}
type I2 interface {
M()
N()
}
func main() {
var v I1
fmt.Println(I2(v))
}

Compiler’s output:

main.go:16: cannot convert v (type I1) to type I2:
I1 does not implement I2 (missing N method)

The reason is as before. If the method set of I2 would be a subset of method set of I1 then compiler would know it at compilation phase. It’s different though and such conversion is possible only while run-time.

It won’t be strictly conversion but type assertion and type switch allowing to check / retrieve dynamic value of interface type value or even convert interface type value to value as of different interface type.

Type assertion

The syntax of type assertion expression is as follows:

v.(T)

where v is of interface type and T is either abstract or concrete type.

Concrete type

Let’s see how it works with non-interface types first (source code):

type I interface {
M()
}
type T struct{}func (T) M() {}func main() {
var v1 I = T{}
v2 := v1.(T)
fmt.Printf("%T\n", v2) // main.T
}

The type specified in type assertion must implement v1’s interface — I. It’s verified at compilation stage (source code):

type I interface {
M()
}
type T1 struct{}func (T1) M() {}type T2 struct{}func main() {
var v1 I = T1{}
v2 := v1.(T2)
fmt.Printf("%T\n", v2)
}

Successful compilation of such code isn’t possible because of impossible type assertion error. Variable v1 cannot hold anything of type T2 since this type doesn’t satisfy interface I and variable v1 can only store values of types implementing I.

The compiler doesn’t know what kind of value is stored inside variable v1 over the course of running the program. Type assertion is a way to retrieve dynamic value from interface type value. But what will happen if the dynamic type of v1 doesn’t match T? (source code)

type I interface {
M()
}
type T1 struct{}func (T1) M() {}type T2 struct{}func (T2) M() {}func main() {
var v1 I = T1{}
v2 := v1.(T2)
fmt.Printf("%T\n", v2)
}

The program will panic:

panic: interface conversion: main.I is main.T1, not main.T2

Multi-valued variant (do not panic please)

Type assertion can be used in multi-valued form where the additional, second value is a boolean indicating if assertion holds or not. If not the first value is zero-value of type T (source code):

type I interface {
M()
}
type T1 struct{}func (T1) M() {}type T2 struct{}func (T2) M() {}func main() {
var v1 I = T1{}
v2, ok := v1.(T2)
if !ok {
fmt.Printf("ok: %v\n", ok) // ok: false
fmt.Printf("%v, %T\n", v2, v2) // {}, main.T2
}
}

This form does’t panic and boolean constant returned as a 2nd value can be used to check if assertion holds or not.

Interface type

In all above cases type used in type assertions was concrete. Golang allows to also pass interface type. It checks if the dynamic value satisfies desired interface and returns value of such interface type value. In contract to conversion, method set of interface passed to type assertion doesn’t have to be a subset of v’s type method set (source code):

type I1 interface {
M()
}
type I2 interface {
I1
N()
}
type T struct{
name string
}
func (T) M() {}
func (T) N() {}
func main() {
var v1 I1 = T{"foo"}
var v2 I2
v2, ok := v1.(I2)
fmt.Printf("%T %v %v\n", v2, v2, ok) // main.T {foo} true
}

If interface is not satisfied then zero-value for interface is returned so nil (source code):

type I1 interface {
M()
}
type I2 interface {
N()
}
type T struct {}func (T) M() {}func main() {
var v1 I1 = T{}
var v2 I2
v2, ok := v1.(I2)
fmt.Printf("%T %v %v\n", v2, v2, ok) // <nil> <nil> false
}

Single-valued variant of type assertion is also supported when dealing with interface types.

nil

When v is nil then type assertion always fails. No matter if T is an interface or a concrete type (source code):

type I interface {
M()
}
type T struct{}func (T) M() {}func main() {
var v1 I
v2 := v1.(T)
fmt.Printf("%T\n", v2)
}

As stated such program will panic:

panic: interface conversion: main.I is nil, not main.T

Introduced earlier multi-value form protects against panic when v is nil —proof.

Type switch

Type assertion is a method to do a single check if dynamic type of an interface type value either implements desired interface or is identical to passed concrete type. If the code needs to do multiple such tests against a single variable then Golang has a construct which is more compact than a series of type assertions and resembles traditional switch statement:

type I1 interface {
M1()
}
type T1 struct{}func (T1) M1() {}type I2 interface {
I1
M2()
}
type T2 struct{}func (T2) M1() {}
func (T2) M2() {}
func main() {
var v I1
switch v.(type) {
case T1:
fmt.Println("T1")
case T2:
fmt.Println("T2")
case nil:
fmt.Println("nil")
default:
fmt.Println("default")
}
}

The syntax is similar to type assertion but type keyword is used. Output is nil (source code) since the value of interface type value is nil but if we’ll set value of v instead:

var v I1 = T2{}

then the program prints T2 (source code). Type switch works also with interface types (source code):

var v I1 = T2{}
switch v.(type) {
case I2:
fmt.Println("I2")
case T1:
fmt.Println("T1")
case T2:
fmt.Println("T2")
case nil:
fmt.Println("nil")
default:
fmt.Println("default")
}

and it prints I2. If there would be more matching cases with interface types then the first one will be used (evaluated top-to-bottom). If there’re no matches then nothing happens (source code):

type I interface {
M()
}
func main() {
var v I
switch v.(type) {
}
}

This program doesn’t panic — it successfully ends its execution.

multiple types per case

Single switch case can specify more than one type, separated by comma. This way code duplication can be avoided if for multiple types the same block should be evaluated (source code):

type I1 interface {
M1()
}
type T1 struct{}func (T1) M1() {}type T2 struct{}func (T2) M1() {}func main() {
var v I1 = T2{}
switch v.(type) {
case nil:
fmt.Println("nil")
case T1, T2:
fmt.Println("T1 or T2")
}
}

This one prints T1 or T2 and good since the dynamic type of v at the moment when guard is evaluated is T2.

default case

This case is similar to good old switch statement. It’s used when no matches have been found (source c0de):

var v I
switch v.(type) {
default:
fmt.Println("fallback")
}

short variable declaration

So far we’ve seen type switches where guard has the syntax:

v.(type)

where v is an expression like variable’s identifier. Additionally short variable declaration can be used there (source code):

var p *T2
var v I1 = p
switch t := v.(type) {
case nil:
fmt.Println("nil")
case *T1:
fmt.Printf("%T is nil: %v\n", t, t == nil)
case *T2:
fmt.Printf("%T is nil: %v\n", t, t == nil)
}

It prints *main.T2 is nil: true so the type of t is the type from case clause. If more than one type is specified in single clause then t’s type is the same as type of v (source code):

var p *T2
var v I1 = p
switch t := v.(type) {
case nil:
fmt.Println("nil")
case *T1, *T2:
fmt.Printf("%T is nil: %v\n", t, t == nil)
}

This one outputs *main.T2 is nil: false. Variable t is of interface type because it’s not nil but points to nil pointer (part I explains when interface type value is nil).

duplicates

Types specified in case clauses must be unique (source code):

switch v.(type) {
case nil:
fmt.Println("nil")
case T1, T2:
fmt.Println("T1 or T2")
case T1:
fmt.Println("T1")
}

Attempt to compile such code will end up with an error duplicate case T1 in type switch.

optional simple statement

Guard can be preceded with simple statement like another short variable declaration (source code):

var v I1 = T1{}
switch aux := 1; v.(type) {
case nil:
fmt.Println("nil")
case T1:
fmt.Println("T1", aux)
case T2:
fmt.Println("T2", aux)
}

This program prints T1 1. Additional simple statement can be used not matter if the guard is in the form of short variable declaration or not.

--

--

Michał Łowicki
golangspec

Software engineer at Datadog, previously at Facebook and Opera, never satisfied.