Blank imports & import side effects

October 29, 2024

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)


Share this

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

Unsure? Browse the archive .

Related Content


Import cycles

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.


An example package

Today we have something a bit different. Just a sample package. An example package Here is a complete Go package that implements a concurrent prime sieve. package main import "fmt" // Send the sequence 2, 3, 4, … to channel 'ch'. func generate(ch chan<- int) { for i := 2; ; i++ { ch <- i // Send 'i' to channel 'ch'. } } // Copy the values from channel 'src' to channel 'dst', // removing those divisible by 'prime'.


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.

Get daily content like this in your inbox!

Subscribe