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 expressionx
. As with type assertions,x
must be of interface type, but not a type parameter, and each non-interface typeT
listed in a case must implement the type ofx
. 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 ofx
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)