Type switching on a non-interface value. Sorta.

May 17, 2024

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 } "}" .
TypeSwitchGuard = [ identifier ":=" ] PrimaryExpr "." "(" "type" ")" .
TypeCaseClause  = TypeSwitchCase ":" StatementList .
TypeSwitchCase  = "case" TypeList | "default" .

There are many details in that paragraph, and some subtleties that are easy to miss. So let’s tear this apart bit by bit. Today, we’ll look at this phrase:

x must be of interface type

This refers to the example we saw yesterday: switch x.(type) {. In other words, this is invalid, and won’t compile:

var x int
switch x.(int) {
}

This might seem obvious, and non-problematic, but it can be a problem when you’re in a generic function:

func toBool[T string|bool](in T) bool {
	switch t := in.(type) {
		case string:
			v, _ := strconv.ParseBool(t)
			return v
		case bool:
			return t
	}
	return false
}

(Playground)

So what’s the solution? Well, fortunately, it’s quite trivial to convert a non-interface value to an interface. Simply replace in with any(in) (or interface{}(in) if you’re using Go 1.17 or older). Now it works!.

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 to non-implementing types

More live coding today! Join me in just a few hours for some more Go TDD work on my kivik library. Today we’re continuing to disect this paragraph: 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.


Switch expressions

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.

Get daily content like this in your inbox!

Subscribe