The init function

So now that we know a little bit about what's happening under the hood, we're going to change things around just a little bit more before we get back into Viper's features.

In Go, the init function is a special function that allows you to establish any kind of state you need to manage for your code to operate before your main function is executed. The link above describes in more detail the exact semantics of when the init() function runs, if you're interested.

We currently have our Viper configuration taking place in our main() function, but it's more common to have this take place in an init() function, or something init-adjacent (like another function that is called by a package's init function). Because Viper tends to represent the global configuration of an application, it's fairly common to include its configuration in the init function of your main.go, or as close to your entrpoint as possible. That ensures that configuration parsing always happens when your entrypoint is called.

Let's move most of our Viper-related function calls over to the init function before we go further.

package main

import // ... unchanged ... 

func main() {
	if err := viper.ReadInConfig(); err != nil {
		// handle this error if you desire
		fmt.Println("config file not found")
	}

	fmt.Println("The log level is set to:", viper.GetBool("logLevel"))
	fmt.Println("logging is enabled:", viper.GetBool("enableLogging"))
}

func init() {
	viper.AddConfigPath(".")
	viper.SetConfigName("config")
	viper.SetConfigType("yaml")
}

You might notice that I didn't move the viper.ReadInConfig call into the init function. You absolutely can do this as well - just make sure to make it the last thing called. For this example, I've left it in main as it makes things easier to represent.

Now back to the config hacking!