Menu Close

init Functions in Go

Most Go Tutorials state every Go program starts at the main function in package main. Unfortunately, that’s not true. Go has init() functions for packages and these get implicitly executed before the main function. This article is going to cover how you can use init functions, and on which properties you should keep an eye on when using them.

Why init functions?

Init functions run before any other code in your program is executed. Thus they’re a perfect fit for:

  • set up or compute initial variables in packages
  • set up connections to external systems like databases
  • initialize packages without having to explicitly call an init function when using it
  • running one-time computation, as they are guaranteed to only run once
  • registering dependencies in other packages like SQL drivers
  • many more …

init function example

An init function is always bound to a package block. It executes at the first import of the package, and it executes only once, regardless of how many times a package got imported.

Let’s look at an example of an init function in the main package:

main.go

package main

import "fmt"

func init() {
	fmt.Println("pkg main func init()")
}

func main() {
	fmt.Println("func main()")
}
Output:
pkg main func init()
func main()

Notice the init() function got implicitly executed before the main function.

Multiple packages

The first example showed an init() function inside the main package. The same applies to imported packages.

a.go

package a

import "fmt"

var A = "A pkg variable"

func init() {
	fmt.Println("pkg a func init()")
}

main.go

package main

import (
	"fmt"
	"init-functions/a"
)

func init() {
	fmt.Println("pkg main func init()")
}

func main() {
	fmt.Println("func main()")

	// just printing this because Go compiler doesn't allow importing unused pkgs
	fmt.Println(a.A)
}

This should output:

Output:
pkg a func init()
pkg main func init()
func main()
A pkg variable

Order of Execution

In general, Go executes init functions sequentially in the order they appear in the source code. Additionally, the following rules apply:

  • If a package imports another package the imported packages init() function runs first
  • If a package depends on variables or functions of another package the dependency is initialized first
  • Go initializes multiple files in a package in the order the files were provided to the compiler. For reproducible builds, Go encourages build systems to provide the files in lexical order.

The official documentation has a in-depth explanation for package initialization: Package Initialization

To emphasize these properties let’s look at some examples:

Multiple init functions in a package

You can specify multiple init() functions in a single package. they’ll run in the order of declaration:

package main

import (
	"fmt"
)

func init() {
	fmt.Println("first pkg main func init()")
}

func init() {
	fmt.Println("second pkg main func init()")
}

func main() {
	fmt.Println("func main()")
}
Output:
first pkg main func init()
second pkg main func init()
func main()

You can even put init() functions in different files of your package. They’ll run in the lexical order of the filenames:

a.go

package test

import "fmt"

var A = "A pkg variable"

func init() {
	fmt.Println("file a func init()")
}

b.go

package test

import "fmt"

var B = "B pkg variable"

func init() {
	fmt.Println("file b func init()")
}

main.go

package main

import (
	"fmt"
	"init-functions/test"
)

func init() {
	fmt.Println("pkg main file init")
}

func main() {
	fmt.Println("func main()")
	fmt.Println(test.A)
}
Output:
file a func init()
file b func init()
pkg main file init
...

If you change the file name of a.go to c.go you’ll get the following output:

Output:
file b func init()
file a func init()
pkg main file init
...

Package imports

The init() functions of imported packages run in the order of the import statements in the source code:

main.go

package main

import (
	"fmt"
	"init-functions/a"
	"init-functions/b"
)

func init() {
	fmt.Println("pkg main file init")
}

func main() {
	fmt.Println("func main()")
	fmt.Println(a.A)
	fmt.Println(b.B)
}
Output:
package A
package B
pkg main file init
...

Change the import order to:

...
	"init-functions/b"
	"init-functions/a"
...
Output:
package B
package A
pkg main file init
...

But if package “b” depends on package “a”, a’s init() function runs first again:

b.go

import (
	"fmt"
	"init-functions/a"
)

var B = "B"

func init() {
	fmt.Println("package B")
	fmt.Println("Print var B from pkg a: ", a.A)
}
Output:
package A
package B
Print var B from pkg a: A
...

It’s better to avoid dependencies between the init functions of packages because it makes the program flow hard to understand.

Nested Package import

For nested package imports the init() functions run in the order of the imports, starting with the deepest package in the import tree:

b.go

package b

import "fmt"

var B = "B"

func init() {
	fmt.Println("package B")
}

a.go

package a

import (
	"fmt"
	_ "init-functions/b"
)

var A = "A"

func init() {
	fmt.Println("package A")
}

main.go

package main

import (
	"fmt"
	_ "init-functions/a"
)

func init() {
	fmt.Println("pkg main file init")
}

func main() {
	fmt.Println("func main()")
}
Output:
package B
package A
pkg main file init

Import packages just for the init function

The Go compiler doesn’t allow to import packages without using it. But as you can see in the last example, we can import a package without using any variables or functions in it, by prefixing it with “_”.

import (
	"fmt"
	_ "init-functions/a"
)

This comes handy if you want to run the init() functions of a package only. A common use case is registering a SQL driver. In most cases the import statement in a DB package looks similar to this:

import (
..
	_ "github.com/mattn/go-sqlite3"
...
)

If we take a look at the source code of this package we can see that the only thing that is done in the init() function is registering the driver with the “sql” package of Go’s standard library.

...
func init() {
	sql.Register("sqlite3", &SQLiteDriver{})
}
...

After this registration, you can work directly with the “sql” package and don’t have to use any functions defined in the driver package.

Conclusion

init() functions are a great way to run set up tasks in your application before the actual program logic executes. When using them, you should keep in mind in which order the init() functions run. Introducing dependencies between init function is a bad idea because it makes it hard to follow the execution flow of your program. It’s possible to run the init() function of a package without using any other variables or function by prefixing your import with “_”.

Leave a Reply

Your email address will not be published.