使用接口作为参数创建另一个包的等效接口

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:

  1. Define RunnerRegisterer (note: this is public) and Runner in one package.
  2. Implement your RunnerCoach in the same package and use the above interfaces. Your RunnerCoach consumes types that implement the interfaces, so it defines them.
  3. Implement your runners in another package. You don't define the Runner interface here.
  4. Implement your 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.

The Solution

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:

  1. Define the type in package B, so it will be available in both packages.
  2. Define the type in package C and have both A and B import package C.
  3. Change your design such that the type is not used in both A and B.

These are your choices whether that type is an interface or any other type.

Choose the option that best fits your design goals.

The Rule / Idiom

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.