I am practicing writing idiomatic Go code and discovered that interfaces should be declared in packages which are consuming them since they're implicit. However I came to this situation where by in the second package (package b) I want a function to call the receiver function of a struct in package a without coupling it tightly.
So naturally, I declare an interface in package b with the signature of the function I want to call from package a. The problem is that this function accepts an argument of a certain type which is an interface declared in package a. As I don't want package b to import package a, I defined an interface in package b with the exact same signature as the one which exists in package a. The playground link below shows the example code.
Playground
package main
import (
"fmt"
"log"
)
func main() {
manager := &Manager{}
coach := NewRunnerCoach(manager)
fmt.Println("Done")
}
// package a
type Runner interface {
Run()
}
type Manager struct {
}
func (o *Manager) RegisterRunner(runner Runner) {
log.Print("RegisterRunner")
}
func (o *Manager) Start() {
log.Print("Start")
}
// package b
type RunnerCoach struct {
runner *FastRunner
}
func NewRunnerCoach(registerer runnerRegisterer) *RunnerCoach {
runnerCoach := &RunnerCoach{&FastRunner{}}
registerer.RegisterRunner(runnerCoach.runner)
return runnerCoach
}
type FastRunner struct {
}
func (r *FastRunner) Run() {
log.Print("FastRunner Run")
}
// define ther registerer interface coach is accepting
type runnerRegisterer interface {
RegisterRunner(runner RunnerB)
}
// declaring a new interface with the same signature because we dont want to import package a
// and import Runner interface
type RunnerB interface {
Run()
}
This code does not compile. So the question here is that, am I using interface wrongly or should concrete types be defined in a separate package or lastly, is there a better code pattern for the problem I'm trying to solve?
EDIT: To clarify, package a and b does not import each other. The main() code exists in a separate package which connects these two.
IIUC, your question is not about packages but boils down to whether a function (or method) can be typecast to another function which takes arguments with equivalent, but not the same interface types.
Something like this: (Go Playground)
package main
type I1 interface{}
func f1(x I1) {}
func main() {
f := (func(interface{}))(f1)
f(nil)
}
Compilation error: ./g.go:8:26: cannot convert f1 (type func(I1)) to type func(interface {})
The answer appears to be no, because Go doesn't consider func (I1)
to be
equivalent to func (interface{})
. The Go spec says this
A function type denotes the set of all functions with the same parameter and result types.
The types func (I1)
and func (interface{})
do not take the same parameters, even though
I1
is defined as interface{}
. Your code fails to compile for a similar
reason because func (runner RunnerB)
is not the same as func (runner Runner)
and hence the method set of *Manager
is not a superset of the
interface runnerRegisterer
.
Coming to your original question:
I am practicing writing idiomatic Go code and discovered that interfaces should be declared in packages which are consuming them since they're implicit.
Yes, the idea is good but it doesn't apply to your implementation the way you
think it does. Since you're expecting to have different implementations of
runnerRegisterer
and they must all have a method with the same signature
using the Runner
interface, it makes sense to define Runner
in a common
place. Also, as seen above Go wouldn't allow you to use a different interface
in the method signatures anyway.
Based on my understanding of what you're trying to achieve, here's how I think you should re-arrange your code:
RunnerRegisterer
(note: this is public) and Runner
in one
package.RunnerCoach
in the same package and use the above
interfaces. Your RunnerCoach
consumes types that implement the interfaces,
so it defines them.Runner
interface here.Manager
in another package, which uses the interface
Runner
defined in RunnerCoach
's package because it must take that type
as an argument if it wants to be used as a RunnerRegisterer
.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With