In Go, is there a way to implement an interface using a method, where the return type of the corresponding method in the implementation is a "wider than" the expected return type?
This is difficult to explain so here is an example. I get this error when running the below example code in Go Playground:
./prog.go:36:14: cannot use BirdImpl{} (type BirdImpl) as type Animal in argument to foo:
BirdImpl does not implement Animal (wrong type for Move method)
have Move() BirdMoveResult
want Move() AnimalMoveResult
(where BirdMoveResult
is "wider than" AnimalMoveResult
because any implementation of BirdMoveResult
is also an implementation of AnimalMoveResult
)
package main
type (
Animal interface {
Move() AnimalMoveResult
}
Bird interface {
Move() BirdMoveResult
}
AnimalMoveResult interface {
GetDistance() int
}
BirdMoveResult interface {
GetDistance() int
GetHeight() int
}
BirdImpl struct{}
BirdMoveResultImpl struct{}
)
// Some implementation of BirdImpl.Move
// Some implementation of BirdMoveResultImpl.GetDistance
// Some implementation of BirdMoveResultImpl.GetHeight
func foo(animal Animal) int {
return animal.Move().GetDistance()
}
func main() {
foo(BirdImpl{}) // This fails because BirdImpl doesn't implement Animal. My question is why not?
}
I understand that the Move()
method signatures do not match completely because of the different return types, thus Go does not consider BirdImpl
as an implementation of Animal
. However, if Go compares the return types, any implementation of BirdMoveResult
would also implement AnimalMoveResult
. So, shouldn't Move() BirdMoveResult
be an acceptable implementation for Move() AnimalMoveResult
(and if not, why not)?
Edit: In the actual scenario, Animal
, AnimalMoveResult
, and foo
are part of an external package. Within my own code, I want to be able to extend AnimalMoveResult
with my own interface methods (as it's done in the example with BirdMoveResult
), while still being able to make use of foo
with using the extended interface.
Best Answer
These kinds of problems are usually the result of:
Your error you have is most specifically related to the 3rd issue, but each is present in your example design here.
Good interfaces should convey (in their name and their method signatures) a set of behaviour. The name of the interface should be implied by method(s) it contains, as the name is simply supposed to capture the essence of that behaviour.
The only interface seemingly needed for this purpose is:
For MoveResult, you can do this (exchange float with int if you like):
This type can potentially function just fine as a struct, given the following assumptions:
Now, implement the bird type:
Now implement foo:
Now use it in main:
Now we can add other kinds of
Mover
s to the program and use them withfoo
without issue.Addressing your comment, where
Animal
,AnimalMoveResult
andfoo
are all external and you can't modify them:How I'm posing the problem with my understanding: There is this function
foo
, designed to accept an interface calledAnimal
, which is characterized by how it returns an proprietary type. You want to use your typeBird
as anAnimal
, but you can't fit all of the behaviour you want into that proprietary return type.So how to resolve this?
Implement your own, wider return type
Now, implement the Bird type
Notice that
Move
returns theAnimalMoveResult
type, so nowBird
will satisfy theAnimal
interface. We are able to do this becausebirdMoveResult
satisfies theAnimalMoveresult
type, so returning it here is legal.The remaining issue is that
Bird
'sMove
method narrows the wide interface, we've effectively lost that new functionality you added. In order to get it back while still satisfying theAnimal
interface, we can't change theMove
method signature at all. Here are 2 possible workarounds.Bird
that returns the wider type, leaving the existingMove
with the same signatureEither of these will work, it's mostly a matter of preference.