Switch expressions

May 10, 2024

Expression switches

If the switch expression evaluates to an untyped constant, it is first implicitly converted to its default type. The predeclared untyped value nil cannot be used as a switch expression. The switch expression type must be comparable.

In this paragraph, “switch expression” refers to the expression that optionally comes after the switch keyword, as in:

switch len(x) {

So first off, if it’s a untyped constant, it’s first converted to its default type. This matters for the comparison to the case expressions. Of course, using constants, typed or untyped, in an switch statement will be somewhat rare, but it is permitted, and can be useful in some cases. But note the exception: Untyped nil cannot be used here (though it can be used as a case expression).

Next, the switch expression must be comparable. So let’s test with a type that’s not comparable, shall we? The spec tells us “[s]lice, map, and function types are not comparable.” So let’s try with one of those:

var f []int
switch f {
default:
	fmt.Println("default")
}

It prints default. See for yourself.

So what gives? Why didn’t it fail to compile?

If we read just a tiny bit further in the spec’s section about comparable types, we see “as a special case, a slice, map, or function value may be compared to the predeclared identifier nil.”

So actually those types are comparable… but only to a single value: nil. I’m not sure how much value there is in a switch statement with at most case nil and default, but hey… it’s legal.

So how can we trigger this prohibition against non-comparable types? With a type parameter!

Consider this code:

func foo[T any](t T) {
	switch t {
	case nil:
		fmt.Println("nil")
	default:
		fmt.Println("not nil")
	}
}

This code won’t compile at all, and the error tells us why:

./prog.go:12:9: cannot switch on t (variable of type T constrained by any) (T is not comparable)

See it in the playground.

Quotes from The Go Programming Language Specification Language version go1.22 (Feb 6, 2024)


Share this

Direct to your inbox, daily. I respect your privacy .

Unsure? Browse the archive .

Related Content


Type switches with generics

More live coding today! Join me! And bring your questions. Type switches … A type parameter or a generic type may be used as a type in a case. If upon instantiation that type turns out to duplicate another entry in the switch, the first matching case is chosen. func f[P any](x any) int { switch x.(type) { case P: return 0 case string: return 1 case []P: return 2 case []byte: return 3 default: return 4 } } var v1 = f[string]("foo") // v1 == 0 var v2 = f[byte]([]byte{}) // v2 == 2 The example included is nice, because it shows two examples of the type parameter P being used: Both the first case (case P) and third (case []P) use the type parameter.


Type switching on a non-interface value. Sorta.

Type switches … Cases then match actual types T against the dynamic type of the expression x. As with type assertions, x must be of interface type, but not a type parameter, and each non-interface type T listed in a case must implement the type of x. The types listed in the cases of a type switch must all be different. TypeSwitchStmt = "switch" [ SimpleStmt ";" ] TypeSwitchGuard "{" { TypeCaseClause } "}" .


Conversion of type parameters

Today I’ll be live-streaming again, doing TDD on an open-source project. I hope you can join! Conversions … Additionally, if T or x’s type V are type parameters, x can also be converted to type T if one of the following conditions applies: Both V and T are type parameters and a value of each type in V’s type set can be converted to each type in T’s type set. Only V is a type parameter and a value of each type in V’s type set can be converted to T.

Get daily content like this in your inbox!

Subscribe