Type switching to non-implementing types

May 20, 2024

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. The types listed in the cases of a type switch must all be different.

Today we’re looking at this specific phrase:

each non-interface type T listed in a case must implement the type of x

What’s this talking about? Let’s look at an example. It’s a bit long, so bear with me:

type Fooer interface {
	Foo()
}

type foo struct{}

func (foo) Foo() {}

type bar struct{}

func (bar) Bar() {}

func main() {
	var x Fooer
	switch x.(type) {
	case foo:
	case bar:
	}
}

Let me break it down. First, I’ve defined an interface type Fooer with a single method Foo(). Then I’ve implemented two different concrete struct types, foo and bar. Of these, only foo implements the Fooer interface—bar doesn’t have a Foo() method.

So with this in place, the switch statement I’ve written is invalid, and won’t compile, giving us the following error:

./prog.go:21:7: impossible type switch case: bar x (variable of type Fooer) cannot have dynamic type bar (missing method Foo)

This is nice, because it points out obvious programming/logic errors at compilation time. Static typing FTW!

However, notice that this restriction applies specifically to “non-interface” types! If we add a Barer interface type to the example, then rewrite the switch statement as so, it no longer errs, even though it exhibits fundamentally the same logic error:

type Barer interface {
	Bar()
}

func main() {
	var x Fooer
	switch x.(type) {
	case foo:
	case Barer:
	}
}

Thy is this allowed?

Because it’s actually possible that the underlying type for x is both a Fooer and a Barer. Maybe it’s underlying type is defined as:

type foobar struct {}
func (foobar) Foo() {}
func (foobar) Bar() {}

In fact, if you think about it, if Go disallowed this construct, it would severly limit the utility of the empty interface (interface{}), as it would be impossible to use an assertion or type switch to convert from interface{} to a broader interface type—something we do virtually all the time in Go.

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


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.


nils in type switches

Type switches … Instead of a type, a case may use the predeclared identifier nil; that case is selected when the expression in the TypeSwitchGuard is a nil interface value. There may be at most one nil case. Straight forward, right? Here’s an example: switch x.(type) { case int: case string: case nil: } But now here’s a question: If we assign x.(type) to a temporary variable the TypeSwitchGuard, what is its type in the nil case?

Get daily content like this in your inbox!

Subscribe