I recently tracked down a bug in my code where I'm basically confused why Go doesn't protect me from it. The code is roughly this (MCVE below):
// common interface for events
interface Event ...
// concrete event type
type ConcreteEvent ...
func EventHandler(e Event) {
// check if the event has a specific type and treat that differently
c, ok := e.(ConcreteEvent)
if ok {
...
} else {
...
}
}
ce := ConcreteEvent{...}
// two calls to the event handler that are both accepted by Go
EventHandler(ce)
EventHandler(&ce)
The first thing that is weird is that both calls are accepted. Ok, as far as I'm concerned, there is no doubt about the meaning, so why not accept both. However, and that is my main concern, the second one will not enter the if
branch inside EventHandler()
but the else
branch. The behaviour switches between the calls if I change the check to c, ok := e.(*ConcreteEvent)
.
My gut feeling is that this is a glitch or bug in the design of Go. However, changing it would change behaviour of existing code, which makes it difficult or impossible to fix in version 1. However, that's not my main concern, which is rather to find these cases and to avoid this error. So, are there any ways (or coding strategies) for avoiding this or at least becoming aware of it?
MCVE
Code
package main
import "fmt"
// common interface for events
type Event interface {
Value() int
}
// concrete event type
type ConcreteEvent struct {
val int
}
func (e ConcreteEvent) Value() int {
return e.val
}
func EventHandler(e Event) {
// check if the event has a specific type and treat that differently
_, ok := e.(ConcreteEvent)
if ok {
fmt.Println("ConcreteEvent")
} else {
fmt.Println("general Event")
}
}
func main() {
ce := ConcreteEvent{
val: 42,
}
// two calls to the event handler that are both accepted by Go
EventHandler(ce)
EventHandler(&ce)
}
Output
ConcreteEvent
general Event
CodePudding user response:
The method set of a pointer type *T
includes the methods declared on the value receiver:
Specs, Method sets
The method set of the corresponding pointer type *T is the set of all methods declared with receiver *T or T (that is, it also contains the method set of T)
So both ConcreteEvent
and *ConcreteEvent
implement the Event
interface.
This is not a "bug" in the language. In fact, it's properly documented. To avoid this issue, you either:
declare the method with the pointer receiver, so that only pointer types will implement the interface
or if both types are always valid, you can type-switch with the double case:
switch t := e.(type) {
case ConcreteEvent, *ConcreteEvent:
fmt.Println("ConcreteEvent", t.Value())
}
Note that in the double case, the type of t
is Event
.
CodePudding user response:
So, are there any ways (or coding strategies) for avoiding this or at least becoming aware of it?
No. This is a non problem.