Type assertion values

December 11, 2023

By now you’ve guessed there was no live stream today. Illess has been hitting my family hard. I’ll try to pick it up again after the new year.

Type assertions

If the type assertion holds, the value of the expression is the value stored in `x` and its type is `T`. If the type assertion is false, a run-time panic occurs. In other words, even though the dynamic type of `x` is known only at run time, the type of `x.(T)` is known to be `T` in a correct program.

``````var x interface{} = 7          // x has dynamic type int and value 7
i := x.(int)                   // i has type int and value 7

type I interface { m() }

func f(y I) {
s := y.(string)        // illegal: string does not implement I (missing method m)
r := y.(io.Reader)     // r has type io.Reader and the dynamic type of y must implement both I and io.Reader
…
}
``````

This should all feel pretty natural. The more interesting bit is what follows:

A type assertion used in an assignment statement or initialization of the special form

``````v, ok = x.(T)
v, ok := x.(T)
var v, ok = x.(T)
var v, ok interface{} = x.(T) // dynamic types of v and ok are T and bool
``````

yields an additional untyped boolean value. The value of ok is `true` if the assertion holds. Otherwise it is `false` and the value of `v` is the zero value for type `T`. No run-time panic occurs in this case.

This is the magic that lets us do some conditional type assertions. Let’s imagine we have a variable `x` of type `any`, and we suspect that the underlying value may be either a `float64` or an `int`, but we don’t know which one. We can find out with some probing:

``````// Is it an integer?
i, ok := x.(int)
if ok {
// Yes, it is!
/* do integery things */
return
}
// or maybe it's a float64?
f, ok := x.(float64)
if ok {
// Yes, it's a float!
/* do floaty things */
return
}
// I guess it's neither one; maybe now we panic, or do some other reasonable thing
panic("x is of an unexpected type")
``````

We can also apply this concept to interfaces, which lets us “smuggle” interfaces. This is something the standard library does in a few places.

Let’s say we have a function that accepts an `io.Reader`, but when we’re done reading, we want to also close the reader if it’s also an `io.Closer`. We can do this with a conditional type assertion:

``````func readAllAndClose(r io.Reader) ([]byte, error) {
if err != nil {
return nil, err
}
// If r is also an io.Closer, lets close it:
if closer, ok := r.(io.Closer); ok {
return closer.Close()
}
// if it's not a closer, just return
return nil
}
``````

Quotes from The Go Programming Language Specification Version of August 2, 2023