简体   繁体   中英

How to solve the import cycle problem in Golang?

I have encountered an import cycle problem. I read some blogs but didn't understand them. This is the sample code that I have written.

file1.go

package dir1

type Filo interface {
    File2(string) string
}

func File1(message string) string {
    var f Filo
    importFile2 := f.File2("file 2")
    return "Welcome to " + message + " and message from file 2: " + importFile2
}

file2.go

package dir2

import "github.com/ibilalkayy/app/dir1"

func File2(message string) string {
    importFile1 := dir1.File1("file 1")
    return "Welcome to " + message + " and message from file 1: " + importFile1
}

main.go

package main

import (
    "fmt"

    "github.com/ibilalkayy/app/dir1"
    "github.com/ibilalkayy/app/dir2"
)

func main() {
    fmt.Println("Hello world")
    first := dir1.File1("file 1")
    second := dir2.File2("file 2")
    fmt.Println(first)
    fmt.Println(second)
}

The desire for an import cycle is usually an indication there is something wrong with the design.

There are 2 general solutions to avoid import cycles:

  • If 2 packages are so closely tied and they depend on each other, it is probably better to merge them (or core functionality) into a single package. This avoids import cycles.
  • Alternatively, one of the packages should use an interface so it does not depend on the other package. This creates a natural ordering of package layers.

For example, the following contrived example won't compile due to the import cycle:

package main

import "example.com/base"
import "example.com/other"

func main() {
   f := &other.Foo{}
   _ = base.Process(f)
}
package base

import "example.com/other"

func Process(f *other.Foo) {
    for {
        if err := f.Frob(); err != nil {
            return err
        }
    }
}
package other

import "example.com/base"

type Foo int

func (f *foo) Frob(name string) error {
    if *f == 0 {
        return errors.New("completed")
    }
    *f--
    return base.Process(f)
}

This can be fixed by changing base to use an interface:

package base

type Frobber interface {
    Frob(string) error
}

func Process(f Frobber) error {
    for {
        if err := f.Frob("some file"); err != nil {
            return err
        }
    }
}

This works since *other.Foo can to passed to base.Process without the base package needing to know anything about the other package.

Another option is to use function variables:

// Given:
package other
import "example.com/base"
type Foo struct {}
func (f *Foo) Frob(name string) error

package base
import "example.com/other"
func Process(f *Foo) error

// Use this instead:
package base
func Process(fn func(string) error) error

// Which enables using:
package main
import "example.com/other"
func main() {
    f := &other.Foo{} // NOTE: This needs to be real an initialised.
    Process(f.Frob)
}

The example in the question is fundamentally broken beyond the import cycle. It will run out of stack space due to mutual recursion. Ignoring that, you can get it to compile with an untyped func . Eg:

func File1(fn func(string) string) string {
    return "Welcome to message from file 2: " + fn("some file")
}

// Call with:

func something(name string) string {
   return "foo/" + name
}

dir1.File1(something)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM