Finally, the last important bit from 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 examining this part:
The types listed in the cases of a type switch must all be different.
This might seem pretty obvious. And it probably is obvious that all of these are invalid, and won’t compile:
switch x.(type) {
case int:
case int:
}
switch x.(type) {
case io.Closer:
case io.Closer:
}
What’s likely less obvious is that you can still get away with logically duplicate types if you try. Or more dangerously, perhaps, if you aren’t paying close attention:
type closer interface{ Close() error }
switch x.(type) {
case io.Closer:
case closer:
}
In this case, io.Closer
has exactly the same definition as our local closer
type. But because they are different named types, they are both allowed.
What’s more, you can have overlapping interface definitions in a switch statement:
switch x.(type) {
case io.Closer:
case io.ReadCloser:
}
This is very important! In the above type switch, any value for x
that implements the Close()
method will not trigger the io.ReadCloser
case, even if it satisfies the io.ReadCloser
interface! In a case like this, you almost certainly want to put the more specific interface first:
switch x.(type) {
case io.ReadCloser:
case io.Closer:
}
And one last note, of something that won’t work, but isn’t always obvious:
switch x.(type) {
case byte:
case uint8:
}
This doesn’t work because byte
is an alias of uint8
. So as far as the compiler is concerned, these types are identical. (as are rune
and int32
, interface{}
and any
, and any derrivitive types of these, such as []byte
and []uint8
or chan<- any
and chan<- interface{}
).
Quotes from The Go Programming Language Specification Language version go1.22 (Feb 6, 2024)