Understanding Init Function in Go With Examples

Init functions are special types of functions that are often used to initialize package level variables or to perform necessary setup of packages.

The init function is invoked automatically by the Go runtime when the containing package is imported. It cannot be called directly from other functions.

Let's explore more about it.

Syntax

The init function is defined using the name init. It does not take any arguments or return any values.

func init() {
    fmt.Println("Syntax of init function")
}

Multiple init functions

Golang provide the flexibility to declare the entire initialization logic in a single function or split across multiple functions. This helps to structure the code in a modular manner and keep the initialization scope isolated to different parts of the program.

In a single file

When you define multiple init functions in a single file, they are executed per the order of declaration in a top-down manner.

Let's take an example to understand this.

// main.go
package main

import "fmt"

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

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

func main() {
  fmt.Println("Main function")
}

Let's run the program:

go run main.go

You will see the following output:

First init function
Second init function
Main function

Across multiple files

When the init functions are defined in separate files, they are executed in the order in which the corresponding files are included in the package.

Here's an example to demonstrate this.

// main.go
package main

import "fmt"

func main() {
  fmt.Println("Main function")
}
// file1.go
package main

import "fmt"

func init() {
  fmt.Println("First init function")
}
// file2.go
package main

import "fmt"

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

Let's build the package and execute the binary:

go build main.go file2.go file1.go
./main

Observe that the output has "Second init function" before "First init function". This is because we placed file2.go before file1.go in the build command.

Keep in mind that if you execute the command go run main.go, then the Go runtime includes the files in the lexicographical order. As a best practice, it's recommended to define init functions with isolated logic, ensuring that their execution order is not dependent on the order of inclusion or compilation of the files.

Where to use init?

Let's look at some practical examples to understand the use cases of init functions.

Example 1

In this example, I create a go module myapp having a main and a logging package. The package variable logger is initialized in the init function inside the package.

// logging.go
package logging

import (
  "log"
  "os"
)

var logger *log.Logger

func init() {
  // Initialize the logger
  logger = log.New(os.Stdout, "[myapp] ", log.Ldate|log.Ltime)
}

func LogInfo(message string) {
  logger.Printf("INFO: %s\n", message)
}

func LogError(err error) {
  logger.Printf("ERROR: %v\n", err)
}
package main

import (
  "fmt"

  "myapp/logging"
)

func main() {
  logging.LogInfo("Starting application...")
  // Do something
  logging.LogError(fmt.Errorf("Something went wrong"))
}

Executing the program:

go run main.go

Observe that the log instance is initialized by default when we import the logging package:

Example 2

Let's take another example to understand the use of init for registering database connection.

// main.go
package main

import (
  "database/sql"
  "log"

  _ "github.com/go-sql-driver/mysql"
)

var (
  DB *sql.DB
)

func init() {
  var err error

  // Connect to Sql server using UDS
  DB, err = sql.Open("mysql", "root@unix(/var/run/mysqld/mysqld.sock)/mydb")
  if err != nil {
  log.Fatal(err)
}

  // Ping the database to ensure the connection is valid
  err = DB.Ping()
  if err != nil {
    log.Fatal(err)
  }

  log.Println("Database connection established")
}

func main() {
  // Use the DB connection
  rows, err := DB.Query("SELECT * FROM users")
  if err != nil {
    log.Fatal(err)
  }
  defer rows.Close()

  // Process the query result
  for rows.Next() {
    var id int
    var username string
    log.Printf("ID: %d, Name: %s\n", id, username)
  }
}

Conclusion

In this tutorial, you looked at the syntax of init function in Go and how it can be used to offload the initializing logic from the main function. You also saw how you could use multiple init functions in a package. However, care must be taken to avoid ordering errors when using multiple init functions.

While init functions provide a great way to segregate the initialization and package registration, it also can create difficult in unit testing, especially when dependencies that need to be mocked are initialized in the init function.

✍️
Madhur Batra is an experienced DevOps engineer who likes to share his learnings and experience with the community.