Import cycles

October 28, 2024

First off, the spec provides an example of how to reference a symbol in an imported package, using different import alias options. I’ve already provided my own examples earlier, and it’s pretty straight forward, so we’ll just breeze through this part.

Import declarations

Consider a compiled a package containing the package clause package math, which exports function Sin, and installed the compiled package in the file identified by "lib/math". This table illustrates how Sin is accessed in files that import the package after the various types of import declaration.

Import declaration          Local name of Sin

import   "lib/math"         math.Sin
import m "lib/math"         m.Sin
import . "lib/math"         Sin

Then we get to a juicy bit. Or maybe an infuriating bit.

An import declaration declares a dependency relation between the importing and imported package. It is illegal for a package to import itself, directly or indirectly, or to directly import a package without referring to any of its exported identifiers.

Some languages do allow circular imports, at least indirectly. That is to say, something like this might be possible in some languages, but isn’t in Go:

In file github.com/project/foo/foo.go:

package foo

import "github.com/project/bar"

And in file github.com/project/bar/bar.go:

package bar

import "github.com/project/foo"

But what if you want a circular import?

Well, you can’t. Simple as that. Ha.

But more seriously, let’s consider a couple alternatives.

  1. First, and most important, try to find a different way to organize your project, so that you don’t need a circular import. Precisely 99.8% of the time (give or take 23.32%), circular imports are the result of poor design. Looking at the solution differently will often reveal a different way to organize your project. You may need to merge some packages, or split a package into different parts. There aren’t really any easy, one-size-fits all solutions here. It takes practice.

  2. There is one neat ugly trick you can use, to get around this restriction in go. Use it sparingly! And that is to use the ‘registry’ pattern. It’s used in the standard library in a few places, it will be the topic of tomorrow’s email. So stay tuned!


Quotes from The Go Programming Language Specification Language version go1.23 (June 13, 2024)


Share this

Direct to your inbox, daily. I respect your privacy .

Unsure? Browse the archive .

Related Content


Blank imports & import side effects

Yesterday I promised to teach you the one dirty trick they don’t want you to know about, to get around Go’s restriction on circular imports. It’s explained in this sentence, the last from the spec on the topic of imports. Import declarations … To import a package solely for its side-effects (initialization), use the blank identifier as explicit package name: import _ "lib/math" Did you catch it? It’s very subtle.


Valid import paths

Today we’re looking at one of the more essoteric parts of the spec. Import declarations … Implementation restriction: A compiler may restrict ImportPaths to non-empty strings using only characters belonging to Unicode’s L, M, N, P, and S general categories (the Graphic characters without spaces) and may also exclude the characters !"#$%&’()*,:;<=>?[]^`{|} and the Unicode replacement character U+FFFD. Other than what’s stated in the quote, we’re not going to look at an exhaustive list of what is and isn’t guaranteed to be supported by a Go implementation.


Relative imports

Newcomers to Go often try to use relative imports, and then are usually bitten by random weird problems. They seem to work sometimes, and not other times. What’s the deal? import "./foo" Import declarations … The interpretation of the ImportPath is implementation-dependent but it is typically a substring of the full file name of the compiled package and may be relative to a repository of installed packages. Okay. So this bit of the spec doesn’t help a whole lot.

Get daily content like this in your inbox!

Subscribe