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.
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
.You've got a type that is used in two packages, A and B. Package A imports package B.
You have to avoid circular dependencies, so you have three choices:
These are your choices whether that type is an interface or any other type.
Choose the option that best fits your design goals.
I am practicing writing idiomatic Go code and discovered that interfaces should be declared in packages which are consuming them since they're implicit.
I get the impulse/idea - the problem is it's not very practical. Interfaces would be less useful if they could only be consumed in the package in which they are defined. The standard library is full of code that violates this axiom. Therefore I do not think the rule as presented -- applying to all interfaces in all situations, designs and contexts -- is idiomatic.
For example, look at the bytes package. func (b *Buffer) ReadFrom(r io.Reader) (n int64, err error)
consumes io.Reader
, an interface defined in another package. func (r *Reader) WriteTo(w io.Writer) (n int64, err error)
consumes io.Writer
, an interface defined in another package.