Understanding init in Go
Understanding init in Go

Understanding init in Go

PostedSeptember 26, 2019 7.9k views Go Development

Introduction

In Go, the predefined init() function sets off a piece of code to run before any other part of your package. This code will execute as soon as the package is imported, and can be used when you need your application to initialize in a specific state, such as when you have a specific configuration or set of resources with which your application needs to start. It is also used when importing a side effect, a technique used to set the state of a program by importing a specific package. This is often used to register one package with another to make sure that the program is considering the correct code for the task.

Although init() is a useful tool, it can sometimes make code difficult to read, since a hard-to-find init() instance will greatly affect the order in which the code is run. Because of this, it is important for developers who are new to Go to understand the facets of this function, so that they can make sure to use init() in a legible manner when writing code.

In this tutorial, you’ll learn how init() is used for the setup and initialization of specific package variables, one time computations, and the registration of a package for use with another package.

Prerequisites

For some of the examples in this article, you will need:

.
├── bin 
│ 
└── src
    └── github.com
        └── gopherguides

Declaring init()

Any time you declare an init() function, Go will load and run it prior to anything else in that package. To demonstrate this, this section will walk through how to define an init() function and show the effects on how the package runs.

Let’s first take the following as an example of code without the init() function:

main.go
package main

import "fmt"

var weekday string

func main() {
    fmt.Printf("Today is %s", weekday)
}

In this program, we declared a global variable called weekday. By default, the value of weekday is an empty string.

Let’s run this code:

  • go run main.go

Because the value of weekday is blank, when we run the program, we will get the following output:

Output
Today is

We can fill in the blank variable by introducing an init() function that initializes the value of weekday to the current day. Add in the following highlighted lines to main.go:

main.go
package main

import (
    "fmt"
    "time"
)

var weekday string

func init() {
    weekday = time.Now().Weekday().String()
}

func main() {
    fmt.Printf("Today is %s", weekday)
}

In this code, we imported and used the time package to get the current day of the week (Now().Weekday().String()), then used init() to initialize weekday with that value.

Now when we run the program, it will print out the current weekday:

Output
Today is Monday

While this illustrates how init() works, a much more typical use case for init() is to use it when importing a package. This can be useful when you need to do specific setup tasks in a package before you want the package to be used. To demonstrate this, let’s create a program that will require a specific initialization for the package to work as intended.

Initializing Packages on Import

First, we will write some code that selects a random creature from a slice and prints it out. However, we won’t use init() in our initial program. This will better show the problem we have, and how init() will solve our problem.

From within your src/github.com/gopherguides/ directory, create a folder called creature with the following command:

  • mkdir creature

Inside the creature folder, create a file called creature.go:

  • nano creature/creature.go

In this file, add the following contents:

creature.go
package creature

import (
    "math/rand"
)

var creatures = []string{"shark", "jellyfish", "squid", "octopus", "dolphin"}

func Random() string {
    i := rand.Intn(len(creatures))
    return creatures[i]
}

This file defines a variable called creatures that has a set of sea creatures initialized as values. It also has an exported Random function that will return a random value from the creatures variable.

Save and quit this file.

Next, let’s create a cmd package that we will use to write our main() function and call the creature package.

At the same file level from which we created the creature folder, create a cmd folder with the following command:

  • mkdir cmd

Inside the cmd folder, create a file called main.go:

  • nano cmd/main.go

Add the following contents to the file:

cmd/main.go
package main

import (
    "fmt"

    "github.com/gopherguides/creature"
)

func main() {
    fmt.Println(creature.Random())
    fmt.Println(creature.Random())
    fmt.Println(creature.Random())
    fmt.Println(creature.Random())
}

Here we imported the creature package, and then in the main() function, used the creature.Random() function to retrieve a random creature and print it out four times.

Save and quit main.go.

We now have our entire program written. However, before we can run this program, we’ll need to also create a couple of configuration files for our code to work properly. Go uses Go Modules to configure package dependencies for importing resources. These modules are configuration files placed in your package directory that tell the compiler where to import packages from. While learning about modules is beyond the scope of this article, we can write just a couple lines of configuration to make this example work locally.

In the cmd directory, create a file named go.mod:

  • nano cmd/go.mod

Once the file is open, place in the following contents:

cmd/go.mod
module github.com/gopherguides/cmd
 replace github.com/gopherguides/creature => ../creature

The first line of this file tells the compiler that the cmd package we created is in fact github.com/gopherguides/cmd. The second line tells the compiler that github.com/gopherguides/creature can be found locally on disk in the ../creature directory.

Save and close the file. Next, create a go.mod file in the creature directory:

  • nano creature/go.mod

Add the following line of code to the file:

creature/go.mod
 module github.com/gopherguides/creature

This tells the compiler that the creature package we created is actually the github.com/gopherguides/creature package. Without this, the cmd package would not know where to import this package from.

Save and quit the file.

You should now have the following directory structure and file layout:

├── cmd
│   ├── go.mod
│   └── main.go
└── creature
    ├── go.mod
    └── creature.go

Now that we have all the configuration completed, we can run the main program with the following command:

  • go run cmd/main.go

This will give:

Output
jellyfish squid squid dolphin

When we ran this program, we received four values and printed them out. If we run the program multiple times, we will notice that we always get the same output, rather than a random result as expected. This is because the rand package creates pseudorandom numbers that will consistently generate the same output for a single initial state. To achieve a more random number, we can seed the package, or set a changing source so that the initial state is different every time we run the program. In Go, it is common to use the current time to seed the rand package.

Since we want the creature package to handle the random functionality, open up this file:

  • nano creature/creature.go

Add the following highlighted lines to the creature.go file:

creature/creature.go
package creature

import (
    "math/rand"
    "time"
)

var creatures = []string{"shark", "jellyfish", "squid", "octopus", "dolphin"}

func Random() string {
    rand.Seed(time.Now().UnixNano())
    i := rand.Intn(len(creatures))
    return creatures[i]
}

In this code, we imported the time package and used Seed() to seed the current time. Save and exit the file.

Now, when we run the program we will get a random result:

  • go run cmd/main.go
Output
jellyfish octopus shark jellyfish

If you continue to run the program over and over, you will continue to get random results. However, this is not yet an ideal implementation of our code, because every time creature.Random() is called, it also re-seeds the rand package by calling rand.Seed(time.Now().UnixNano()) again. Re-seeding will increase the chance of seeding with the same initial value if the internal clock has not changed, which will cause possible repetitions of the random pattern, or will increase CPU processing time by having your program wait for the clock to change.

To fix this, we can use an init() function. Let’s update the creature.go file:

  • nano creature/creature.go

Add the following lines of code:

creature/creature.go
package creature

import (
    "math/rand"
    "time"
)

var creatures = []string{"shark", "jellyfish", "squid", "octopus", "dolphin"}

func init() {
    rand.Seed(time.Now().UnixNano())
}

func Random() string {
    i := rand.Intn(len(creatures))
    return creatures[i]
}

Adding the init() function tells the compiler that when the creature package is imported, it should run the init() function once, providing a single seed for the random number generation. This ensures that we don’t run code more than we have to. Now if we run the program, we will continue to get random results:

  • go run cmd/main.go
Output
dolphin squid dolphin octopus

In this section, we have seen how using init() can ensure that the appropriate calculations or initializations are performed prior to the package being used. Next, we will see how to use multiple init() statements in a package.

Multiple Instances of init()

Unlike the main() function that can only be declared once, the init() function can be declared multiple times throughout a package. However, multiple init()s can make it difficult to know which one has priority over the others. In this section, we will show how to maintain control over multiple init() statements.

In most cases, init() functions will execute in the order that you encounter them. Let’s take the following code as an example:

main.go
package main

import "fmt"

func init() {
    fmt.Println("First init")
}

func init() {
    fmt.Println("Second init")
}

func init() {
    fmt.Println("Third init")
}

func init() {
    fmt.Println("Fourth init")
}

func main() {}

If we run the program with the following command:

  • go run main.go

We’ll receive the following output:

Output
First init Second init Third init Fourth init

Notice that each init() is run in the order in which the compiler encounters it. However, it may not always be so easy to determine the order in which the init() function will be called.

Let’s look at a more complicated package structure in which we have multiple files each with their own init() function declared in them. To illustrate this, we will create a program that shares a variable called message and prints it out.

Delete the creature and cmd directories and their contents from the earlier section, and replace them with the following directories and file structure:

├── cmd
│   ├── a.go
│   ├── b.go
│   └── main.go
└── message
    └── message.go

Now let’s add the contents of each file. In a.go, add the following lines:

cmd/a.go
package main

import (
    "fmt"

    "github.com/gopherguides/message"
)

func init() {
    fmt.Println("a ->", message.Message)
}

This file contains a single init() function that prints out the value of message.Message from the message package.

Next, add the following contents to b.go:

cmd/b.go
package main

import (
    "fmt"

    "github.com/gopherguides/message"
)

func init() {
    message.Message = "Hello"
    fmt.Println("b ->", message.Message)
}

In b.go, we have a single init() function that set’s the value of message.Message to Hello and prints it out.

Next, create main.go to look like the following:

cmd/main.go
package main

func main() {}

This file does nothing, but provides an entry point for the program to run.

Finally, create your message.go file like the following:

message/message.go
package message

var Message string

Our message package declares the exported Message variable.

To run the program, execute the following command from the cmd directory:

  • go run *.go

Because we have multiple Go files in the cmd folder that make up the main package, we need to tell the compiler that all the .go files in the cmd folder should be compiled. Using *.go tells the compiler to load all the files in the cmd folder that end in .go. If we issued the command of go run main.go, the program would fail to compile as it would not see the code in the a.go and b.go files.

This will give the following output:

Output
a -> b -> Hello

Per the Go language specification for Package Initialization, when multiple files are encountered in a package, they are processed alphabetically. Because of this, the first time we printed out message.Message from a.go, the value was blank. The value wasn’t initialized until the init() function from b.go had been run.

If we were to change the file name of a.go to c.go, we would get a different result:

Output
b -> Hello a -> Hello

Now the compiler encounters b.go first, and as such, the value of message.Message is already initialized with Hello when the init() function in c.go is encountered.

This behavior could create a possible problem in your code. It is common in software development to change file names, and because of how init() is processed, changing file names may change the order in which init() is processed. This could have the undesired effect of changing your program’s output. 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. One way to ensure that all init() functions are loaded in order is to declare them all in one file. This will prevent the order from changing even if file names are changed.

In addition to ensuring the order of your init() functions does not change, you should also try to avoid managing state in your package by using global variables, i.e., variables that are accessible from anywhere in the package. In the preceding program, the message.Message variable was available to the entire package and maintained the state of the program. Because of this access, the init() statements were able to change the variable and destablize the predictability of your program. To avoid this, try to work with variables in controlled spaces that have as little access as possible while still allowing the program to work.

We have seen that you can have multiple init() declarations in a single package. However, doing so may create undesired effects and make your program hard to read or predict. Avoiding multiple init() statements or keeping them all in one file will ensure that the behavior of your program does not change when files are moved around or names are changed.

Next, we will examine how init() is used to import with side effects.

Using init() for Side Effects

In Go, it is sometimes desirable to import a package not for its content, but for the side effects that occur upon importing the package. This often means that there is an init() statement in the imported code that executes before any of the other code, allowing for the developer to manipulate the state in which their program is starting. This technique is called importing for a side effect.

A common use case for importing for side effects is to register functionality in your code, which lets a package know what part of the code your program needs to use. In the image package, for example, the image.Decode function needs to know which format of image it is trying to decode (jpg, png, gif, etc.) before it can execute. You can accomplish this by first importing a specific program that has an init() statement side effect.

Let’s say you are trying to use image.Decode on a .png file with the following code snippet:

Sample Decoding Snippet
. . .
func decode(reader io.Reader) image.Rectangle {
    m, _, err := image.Decode(reader)
    if err != nil {
        log.Fatal(err)
    }
    return m.Bounds()
}
. . .

A program with this code will still compile, but any time we try to decode a png image, we will get an error.

To fix this, we would need to first register an image format for image.Decode. Luckily, the image/png package contains the following init() statement:

image/png/reader.go
func init() {
    image.RegisterFormat("png", pngHeader, Decode, DecodeConfig)
}

Therefore, if we import image/png into our decoding snippet, then the image.RegisterFormat() function in image/png will run before any of our code:

Sample Decoding Snippet
. . .
import _ "image/png"
. . .

func decode(reader io.Reader) image.Rectangle {
    m, _, err := image.Decode(reader)
    if err != nil {
        log.Fatal(err)
    }
    return m.Bounds()
}

This will set the state and register that we require the png version of image.Decode(). This registration will happen as a side effect of importing image/png.

You may have noticed the blank identifier (_) before "image/png". This is needed because Go does not allow you to import packages that are not used throughout the program. By including the blank identifier, the value of the import itself is discarded, so that only the side effect of the import comes through. This means that, even though we never call the image/png package in our code, we can still import it for the side effect.

It is important to know when you need to import a package for its side effect. Without the proper registration, it is likely that your program will compile, but not work properly when it is run. The packages in the standard library will declare the need for this type of import in their documentation. If you write a package that requires importing for side effect, you should also make sure the init() statement you are using is documented so users that import your package will be able to use it properly.

Conclusion

In this tutorial we learned that the init() function loads before the rest of the code in your package is loaded, and that it can perform specific tasks for a package like initializing a desired state. We also learned that the order in which the compiler executes multiple init() statements is dependent on the order in which the compiler loads the source files. If you would like to learn more about init(), check out the official Golang documentation, or read through the discussion in the Go community about the function.

You can read more about functions with our How To Define and Call Functions in Go article, or explore the entire How To Code in Go series.

0 Comments

Creative Commons License