Recently I started to learn the Go language.
I am trying to understand interface principles in Go and was completely puzzled by one thing.
The duck principle says: if something quacks like a duck and walks like a duck, then it's a duck.
But I wondered how Go will behave if we have three interfaces like this:
// Interface A
type InterfaceA interface {
ActionA() string
}
// Interface B
type InterfaceB interface {
ActionB() string
}
And interface C
, which does something different but has functions which are similar to interfaces A
and B
functions:
// Interface C with methods A and B interfaces
type InterfaceC interface {
ActionA() string
ActionB() string
}
Then we have three structures which implement the interfaces above:
type StructA struct{}
// If it does ActionA then it's interface A
func (a StructA) ActionA() string {
return "Interface A implementation"
}
type StructB struct{}
// If it does ActionB then it's interface B
func (b StructB) ActionB() string {
return "Interface B implementation"
}
type StructC struct{}
// If it does ActionA and ActionB, it's an Interface C
func (c StructC) ActionA() string {
return "Interface C implementation"
}
func (c StructC) ActionB() string {
return "Interface C implementation"
}
And a function that identifies which type it gets:
func getType(data interface{}) string {
switch data.(type) {
default:
return "Unknown"
case InterfaceA:
return "Interface A"
case InterfaceB:
return "Interface B"
case InterfaceC:
return "Interface C"
}
}
Code inside main
function:
func main() {
a := StructA{}
fmt.Println(a.ActionA())
fmt.Println(getType(a)) // should return InterfaceA
fmt.Println("")
b := StructB{}
fmt.Println(b.ActionB())
fmt.Println(getType(b)) // should return InterfaceB
fmt.Println("")
c := StructC{}
fmt.Println(c.ActionA())
fmt.Println(c.ActionB())
fmt.Println(getType(c)) // should return InterfaceC
}
Output:
Interface A implementation
Interface A
Interface B implementation
Interface B
Interface C implementation
Interface C implementation
Interface A
After some experiments I found out if we change the case
order inside switch
then the function identifies the type correctly:
func getType(data interface{}) string {
switch data.(type) {
default:
return "Unknown"
case InterfaceC:
return "Interface C"
case InterfaceB:
return "Interface B"
case InterfaceA:
return "Interface A"
}
}
Output:
Interface A implementation
Interface A
Interface B implementation
Interface B
Interface C implementation
Interface C implementation
Interface C
See also full code on play.golang.org
My question: Is it a bug or a feature? And if it's a feature, how should I change getType
so that the function doesn't depend on case
order?
This is the intended working, as defined by the language spec.
There are 2 types of switch
statement, Expression switches and Type switches, and this behavior is documented at the Expression switches:
In an expression switch, the switch expression is evaluated and the case expressions, which need not be constants, are evaluated left-to-right and top-to-bottom; the first one that equals the switch expression triggers execution of the statements of the associated case; the other cases are skipped. If no case matches and there is a "default" case, its statements are executed. There can be at most one default case and it may appear anywhere in the "switch" statement.
[...]
A type switch compares types rather than values. It is otherwise similar to an expression switch.
In Go a type implicitly implements an interface if its method set is a superset of the interface. There is no declaration of the intent. So in Go it doesn't matter which interface defines the methods, only thing that matters is the method signatures: if a type has all the methods an interface "prescribes", then that type implicitly implements the said interface.
The problem is that you want to use the Type switch to something it was not designed for. You want to find the "widest" type (with the most methods) that is still implemented by the value. It will only do this if you enumerate the cases (the different types) in this intended order.
That being said, in your case there's no such thing that a value is only InterfaceC
implementation. Your code doesn't lie: all values that implement InterfaceC
will also implement InterfaceA
and InterfaceB
too, because the method sets of both InterfaceA
and InterfaceB
are subsets of the method set of InterfaceC
.
If you want to be able to "differentiate" InterfaceC
implementations, you have to "alter" the method sets so that the above mentioned relation will not hold (method set of InterfaceC
will not be a superset of the method set of InterfaceA
and InterfaceB
). If you want StructC
to not be an InterfaceA
implementation, you must change the method signature of ActionA()
(either in InterfaceA
or in InterfaceC
), and similarly ActionB()
to not be an InterfaceB
implementation.
You could also add a method to InterfaceA
(and to InterfaceB
) which is missing from InterfaceC
:
type InterfaceA interface {
ActionA() string
implementsA()
}
type InteraceB interface {
ActionB() string
implementsB()
}
And you have to add them to StructA
and StructB
of course:
func (a StructA) implementsA() {}
func (b StructB) implementsB() {}
This way you get the desired output. Try it on the Go Playground.
If you can't or don't want to do this, your only option is to enumerate the cases in the right order. Or don't use type switch for this.