Type assertions and type switches
Getting the concrete value back out of an interface, safely.
Topic 18 of 98 · 5 min to read · 45 min to build
After this topic you can
- Write a comma-ok type assertion that never panics.
- Write a type switch that handles several concrete types and a default.
- Fix a type switch where a broad interface case hides a more specific one.
The problem
An interface value hides what is inside it. A variable of type any might hold an int, a string, or your own struct. Most of the time that is the point: you call the methods and do not care.
Sometimes you do care. A type assertion asks "is the value inside this interface of type T?", and if so gives it back to you as a T.
Type assertions, and the comma-ok form
assert.go
package mainimport "fmt"func main() { var v any = "hello" s := v.(string) // "I am sure it is a string" fmt.Println(s, len(s)) n, ok := v.(int) // "is it an int?" — no panic, just ok=false fmt.Println(n, ok) // 0 false if s, ok := v.(string); ok { fmt.Println("it was a string:", s) } // v.(int) without the ok would panic: // interface conversion: interface {} is string, not int}Type switches
When there are several types to check, a type switch does all the assertions in one go. Inside each case, the variable already has that case’s type.
switch.go
package mainimport "fmt"type Point struct{ X, Y int }func describe(v any) string { switch x := v.(type) { case nil: return "nothing" case int: return fmt.Sprintf("an int, doubled is %d", x*2) // x is an int here case string: return fmt.Sprintf("a string of %d bytes", len(x)) // x is a string here case Point: return fmt.Sprintf("a point at %d,%d", x.X, x.Y) case error: return "an error: " + x.Error() // cases can be interfaces too default: return fmt.Sprintf("something else: %T", x) }}func main() { fmt.Println(describe(nil)) fmt.Println(describe(21)) fmt.Println(describe("héllo")) fmt.Println(describe(Point{1, 2})) fmt.Println(describe(fmt.Errorf("boom"))) fmt.Println(describe(3.5))}Errors are often wrapped inside other errors, which a plain type assertion cannot see through. The tool for that is covered in Error values in depth.
Watch out: A long type switch over your own types is often a sign that a method is missing. If every case calls a different "area" formula, give the types an
Area() method and an interface instead. Switch on types at the edges, where values arrive as any (JSON, config, errors).The short version
v.(T)gets a T out of an interface, and panics if it is not one.t, ok := v.(T)never panics.oktells you whether it worked.switch x := v.(type)checks many types at once; in each casexhas that type.- Cases run top to bottom, so put specific types before broad interfaces.
Remember: Comma-ok unless a wrong type is a bug.
Build this before moving on
Write func sum(values []any) (float64, error) that adds up ints, float64s and numeric strings (use strconv.ParseFloat), and returns an error naming the first value of any other type.Coding challenge · checked by tests
Write
Describe(v any) string with a type switch. An int gives "int N", a string gives "string of N bytes", a bool gives "bool", and anything else gives "unknown". Examples: 21 → "int 21", "héllo" → "string of 6 bytes", true → "bool", 3.5 → "unknown".Tab indents. Press Esc, then Tab, to leave the editor.
Then try these in your own editor
- 1Assert
any(42).(string)without comma-ok and read the panic. - 2In a type switch, put
case error:abovecase *MyError:and see what happens. - 3Write a case that lists two types,
case int, int64:, and print%Tof the variable inside it.
Official documentation