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 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.


Exact & loose type unification

Type unification … Unification uses a combination of exact and loose unification depending on whether two types have to be identical, assignment-compatible, or only structurally equal. The respective type unification rules are spelled out in detail in the Appendix. The precise definitions of “exact” and “loose” unification are buried in the appendix, and depend on the specific types involved. In general, I think it’s not terribly inaccurate to say that exact unification applies when the two types are identical, for composite types with identical structure (i.