init functions in Go

Michał Łowicki
golangspec

--

Identifier main is ubiquitous. Every Go program starts in a package main by calling identically named function. When this function returns the program ends its execution. Functions init also play special role and this post will describe their properties and introduce how to use them.

init functions are defined in package block and are used for:

  • variables initialization if cannot be done in initialization expression,
  • checking / fixing program’s state,
  • registering,
  • running one-time computations,
  • and many more.

Besides some differences discussed below you can put there anything which is valid inside regular function.

Package initialization

To use a imported package it needs to be initialized first. It’s done by Golang’s runtime system and consists of (order matters):

  1. initialization of imported packages (recursive definition)
  2. computing and assigning initial values for variables declared in a package block
  3. executing init functions inside the package

Package initialization is done only once even if package is imported many times.

The order

Package in Go can contain many files. What is the order of variables initialization and init functions calls if they’re spread across many package’s files? First, initialization dependency machinery kicks in (more in “Initialization dependencies in Go”). When this done there needs to be a decision made if initialize variables in file a.go or maybe z.go should be handled earlier. This depends on the order of files presented to the compiler. If z.go is first passed by build system then variables initializations are done there before doing the same in a.go. The same applies to triggering init functions. Language specification recommends to always use the same order and pass file names from package in lexical order:

To ensure reproducible initialization behavior, build systems are encouraged to present multiple files belonging to the same package in lexical file name order to a compiler.

but relying on particular order is rather a path for less portable programs. Let’s see an example how it all works together:

sandbox.go

package mainimport "fmt"var _ int64 = s()func init() {
fmt.Println("init in sandbox.go")
}
func s() int64 {
fmt.Println("calling s() in sandbox.go")
return 1
}
func main() {
fmt.Println("main")
}

a.go

package mainimport "fmt"var _ int64 = a()func init() {
fmt.Println("init in a.go")
}
func a() int64 {
fmt.Println("calling a() in a.go")
return 2
}

z.go

package mainimport "fmt"var _ int64 = z()func init() {
fmt.Println("init in z.go")
}
func z() int64 {
fmt.Println("calling z() in z.go")
return 3
}

Program outputs:

calling a() in a.go
calling s() in sandbox.go
calling z() in z.go
init in a.go
init in sandbox.go
init in z.go
main

Properties

init function doesn’t take arguments neither returns any value. In contrast to main, identifier init is not declared so cannot be referenced:

package mainimport "fmt"func init() {
fmt.Println("init")
}
func main() {
init()
}

and it gives “undefined: init” error while compilation.

Formally speaking init identifier doesn’t introduce binding. In the same way works blank identifier represented by underscore character.

Many init functions can be defined in the same package or file:

sandbox.go

package mainimport "fmt"func init() {
fmt.Println("init 1")
}
func init() {
fmt.Println("init 2")
}
func main() {
fmt.Println("main")
}

utils.go

package mainimport "fmt"func init() {
fmt.Println("init 3")
}

outputs:

init 1
init 2
init 3
main

init functions are used frequently across standard library f.ex. in math, bzip2 or image packages.

The most common use case of init function is to assign a value which cannot be calculated as a part of initialization expression:

var precomputed = [20]float64{}func init() {
var current float64 = 1
precomputed[0] = current
for i := 1; i < len(precomputed); i++ {
precomputed[i] = precomputed[i-1] * 1.2
}
}

It’s not possible to use for loop as an expression (it’s a statement in Go) so putting it into init function solves the problem.

Importing a package only for its side effects

Go is very strict when it goes to unused imports. Sometimes a programmer might want to import a package only to call init function(s) doing f.ex. some bootstrapping work. Blank identifier comes to the rescue then:

import _ "image/png"

It’s even mentioned in comments for e.g. image package.

If stuff above float your boat please follow me to get updates about future stories and boost my motivation.

--

--

Michał Łowicki
golangspec

Software engineer at Datadog, previously at Facebook and Opera, never satisfied.