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.
In Go, the init() function is incredibly powerful and compared to some other languages, is a lot easier to use within your Go programs.
These init() functions can be used within a package block and regardless of how many times that package is imported, the init() function will only be called once.
Now, the fact that it is only called once is something you should pay close attention to. This effectively allows us to set up database connections, or register with various service registries, or perform any number of other tasks that you typically only want to do once.
In this example, we can start to see why using the init() function would be preferential when compared to having to explicitly call your own setup functions.
When we run the above program, you should see that our name variable has been properly set and whilst it’s not the most useful variable on the planet, we can certainly still use it throughout our Go program.
Let’s have a look at a more complex scenario that is closer to what you’d expect in a production Go system. Imagine we had 4 distinct Go packages within our application, main, broker, and database.
In each of these we could specify an init() function which would perform the task of setting up the connection pool to the various 3rd party services such as Kafka or MySQL.
Whenever we then call a function in our database package, it would then use the connection pool that we set up in our init() function.
For more complex systems, you may have more than one file making up any given package. Each of these files may indeed have their own init() functions present within them. So how does Go order the initialization of these packages?
When it comes to the order of the initialization, a few things are taken into consideration. Things in Go are typically initialized in the order in declaration order but explicitly after any variables they may depend on. This means that, should you have 2 files a.go and b.go in the same package, if the initialization of anything in a.go depends on things in b.go they will be initialized first.
Pretty cool, huh? But what is this for? Why do we allow this? Well, for more complex systems, it allows us to break up complex initialization procedures into multiple, easier-to-digest init() functions.
It essentially allows us to avoid having one monolithic code block in a single init() function which is always a good thing. The one caveat of this style is that you will have to take care when ensuring declaration order.