Temporary variables in type switches

May 22, 2024

Type switches

TypeSwitchStmt  = "switch" [ SimpleStmt ";" ] TypeSwitchGuard "{" { TypeCaseClause } "}" .
TypeSwitchGuard = [ identifier ":=" ] PrimaryExpr "." "(" "type" ")" .
TypeCaseClause  = TypeSwitchCase ":" StatementList .
TypeSwitchCase  = "case" TypeList | "default" .

The TypeSwitchGuard may include a short variable declaration. When that form is used, the variable is declared at the end of the TypeSwitchCase in the implicit block of each clause. In clauses with a case listing exactly one type, the variable has that type; otherwise, the variable has the type of the expression in the TypeSwitchGuard.

The temporary variable created with a short variable declaration in a type switch is incredibly powerful. But there are also some subtleties. To explain what the above paragraph is describing, let’s look at a simple example.

switch t := x.(type) {
case string
	/* here t is of type string */
case int:
	/* here t is of type int */
default:
	/* here t is of the same interface type as x */
}

So that seems pretty straight forward… what subtleties could exist?

Let’s imagine we’re trying to convert from an arbitrary interface value to an integer:

func toInt(x any) int {
	switch t := x.(type) {
	case int:
		return t
	case int8:
		return int(t)
	case int16:
		return int(t)
	/* .. skip similar cases for int32, int64, uint8, float32, etc... */
	default:
		panic(fmt.Sprintf("cannot convert type %T to int", x))
	}
}

Notice that except for case int, every other case (included the many omitted cases) are identical. One might feel justified in trying to shorten this, therefore, to:

func toInt(x any) int {
	switch t := x.(type) {
	case int:
		return t
	case int8, int16, int32, int64, uint8, uint16, uint32, uint64, uint, float32, float64:
		return int(t)
	default:
		panic(fmt.Sprintf("cannot convert type %T to int", x))
	}
}

Looks a lot better, right?

But it doesn’t work!

cannot convert t (variable of type any) to type int: need type assertion

This is because a case clause with multiple types cannot assign a single type to t, so it reverts to the same type as x—the same behavior as the default clause.

So in a case like this, you’ll be left writing a bunch of otherwise redundant case clauses.

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 .

Get daily content like this in your inbox!

Subscribe