We’ve been looking at type switches since last week. Today the spec shows us a large example, comparing type switches to the equivalent expressed without them.
Type switches
…
Given an expression
x
of typeinterface{}
, the following type switch:switch i := x.(type) { case nil: printString("x is nil") // type of i is type of x (interface{}) case int: printInt(i) // type of i is int case float64: printFloat64(i) // type of i is float64 case func(int) float64: printFunction(i) // type of i is func(int) float64 case bool, string: printString("type is bool or string") // type of i is type of x (interface{}) default: printString("don't know the type") // type of i is type of x (interface{}) }
could be rewritten:
v := x // x is evaluated exactly once if v == nil { i := v // type of i is type of x (interface{}) printString("x is nil") } else if i, isInt := v.(int); isInt { printInt(i) // type of i is int } else if i, isFloat64 := v.(float64); isFloat64 { printFloat64(i) // type of i is float64 } else if i, isFunc := v.(func(int) float64); isFunc { printFunction(i) // type of i is func(int) float64 } else { _, isBool := v.(bool) _, isString := v.(string) if isBool || isString { i := v // type of i is type of x (interface{}) printString("type is bool or string") } else { i := v // type of i is type of x (interface{}) printString("don't know the type") } }
Let’s walk through this example.
v := x // x is evaluated exactly once
The comment says it all, but it’s important: x.(type)
is not evaluated for each case
statement; it’s evaluated only once.
if v == nil { i := v // type of i is type of x (interface{}) printString("x is nil") } else …
This code wont’ actually compile. It’s creating a variable (i
) that isn’t used. But for illustration purposes, it’s valid, and demonstrates that within the case nil
block, i
’s type and value are both the same as that of x
, or interface{}
.
… else if i, isInt := v.(int); isInt { printInt(i) // type of i is int } else if i, isFunc := v.(func(int) float64); isFunc { printFunction(i) // type of i is func(int) float64 } else …
In both of these cases, i
has a type distinct from x
—it’s type is int
or float64
, respectively, depending on which, if either, of the type assertions evaluates to true.
… else { _, isBool := v.(bool) _, isString := v.(string) if isBool || isString { i := v // type of i is type of x (interface{}) printString("type is bool or string") } else { i := v // type of i is type of x (interface{}) printString("don't know the type") } }
This code snippet handles both case bool, string
and default
. Let’s look at these individually:
_, isBool := v.(bool) _, isString := v.(string) if isBool || isString { i := v // type of i is type of x (interface{}) printString("type is bool or string") } else …
This case demonstrates the behavior of a type switch case with multiple types: case bool, string
. The important thing to notice is that i
’s type and value are, once again, identical to that of x
. This means we cannot use i
in any way that depends on its specific value. For example, it could not be passed to printString
direction as printString(i)
(assuming a function signature of printString(string)
).
… { i := v // type of i is type of x (interface{}) printString("don't know the type") } }
And finally, the equivalent of the default
case. Once again, i
is identical to x
.
Quotes from The Go Programming Language Specification Language version go1.22 (Feb 6, 2024)