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.
It’s hidden in the word “side-effects.”
What are side effects with respect to package imports?

During the initialization of a package, some code can be executed (for example, in an init
function). This code may have side effects. And if you want to experience those side effects, but don’t need to access any unexported symbols, you can accomplish this by using the blank identifier (_
) as the package name in the import statement.
And that brings us to the nasty little tricky to get around circular imports, which probably the most common reason to use a so-called “blank import”.
If you’ve ever used the database/sql
or image
packages from the standard library, you’ve likely used this pattern, even if you didn’t realize it.
Let’s consider the image
package as an example. image
provides some general purpose image manipulation capabilities, but doesn’t understand any specific image formats (GIF, PNG, etc). Those format definitions are handled in separate packages (image/gif
, image/png
, etc).
But image
needs access to format-specific information. And each format needs access to image
’s generalizations. Import cycle here we come!
This is solved by image
exporting a special function, RegisterFormat
.
Then each format, such as image/png
(or even your own custom format, if you wish to create one), can import the image
package, and call that function in its own init
function. Here’s the one from image/png
:
func init() {
image.RegisterFormat("png", pngHeader, Decode, DecodeConfig)
}
In this way image/png
is effectively doing dependency-injection into the image
package at runtime. And it breaks the circular import, as there’s no longer a need for image
to import image/png
(or all the other formats that may exist now–or in the future). It also makes it much more extensible (one of the benefits of dependency injection, in general).
Quotes from The Go Programming Language Specification Language version go1.23 (June 13, 2024)