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.