Package initialization

When we run a Go program, the Go compiler follows a certain execution order for packages, files in a package and variable declaration in those files.

Package scope

A scope is a region in a code block where a defined variable is accessible. A package scope is a region within a package where a declared variable is accessible from within a package (across all the files in the package). This region is the top-most block of any file in the package.

Take a look at go run command. This time, instead of executing one file, we have a glob pattern to include all the files inside app package for execution.

Go is smart enough to figure out an entry point of the application which is entry.go because it has main function. We can also use a command like below (the filename order doesn’t matter).

go run src/app/version.go src/app/entry.go

💡 You can also use go run app command which does the same thing and this is a better approach to execute a package. go install or go build command requires a package name, which includes all the files inside a package, so we don’t have to specify them like above.

Coming back to our main issue, we can use variable version declared in version.go file from anywhere in the package even though it is not exported (like Version ), because it is declared in package scope.

If the version variable would have been declared inside a function, it wouldn’t have been in the package scope and the above program would have failed to compile.

You are not allowed to redeclare a global variable with the same name in the same package. Hence, once version variable is declared, it can not be redeclared in the package scope. But you are free to re-declare elsewhere.

Variable initialization

When a variable a depends on another variable b , b should be defined beforehand, else program won’t compile. Go follows this rule inside functions.

But when these variables are defined in package scope, they are declared in initialization cycles. Let’s have a look at the simple example below.

In the above example, first c is declared because its value is already declared. In later initialization cycle, b is declared, because it depends on c and value of c is already declared. In the final initialization cycle, a is declared and assigned to the value of b . Go can handle complex initialization cycles like below.

In the above example, first c will be declared and then b as its value depends on c and finally a as its value depends on b . You should avoid any initialization loop like below where initialization gets into a recursive loop.

Another example of package scope would be, having a function f in a separate file which references variable c from the main file.

Init function

Like main function, init function is called by Go when a package is initialized. It does not take any arguments and doesn’t return any value.

The init function is implicitly declared by Go, hence you can not reference it from anywhere (or call it like init() ). You can have multiple init functions in a file or a package. Order of the execution of init function in a file will be according to the order of their appearances.

You can have init function anywhere in the package. These init functions are called in lexical file name order (alphabetical order).

After all the init functions are executed, main function is called. Hence, the main job of init function is to initialize global variables that cannot be initialized in the global context. For example, initialization of an array.

Since, for syntax is not valid in package scope, we can initialize the array integers of size 10 using for loop inside init function.

Package alias

When you import a package, Go create a variable using the package declaration of the package. If you are importing multiple packages with the same name, this will lead to a conflict.

// greet/parent.go

package greet var Message = "Hey there. I am parent." // greet/greet/child.go

package greet var Message = "Hey there. I am child."

Hence, we use package alias. We state a variable name in between import keyword and package name which becomes the new variable to reference the package.

In the above example, greet/greet package now is referenced by child variable. If you notice, we aliased greet package with an underscore.

Underscore is a special character in Go which acts as null container. Since we are importing greet package but not using it, Go compiler will complain about it. To avoid that, we are storing reference of that package into _ and Go compiler will simply ignore it.

Aliasing a package with an underscore which seems to do nothing is quite useful sometimes when you want to initialize a package but not use it.

// greet/parent.go

package greet import "fmt" var Message = "Hey there. I am parent." func init() {

fmt.Println("greet/parent.go ==> init()")

} // greet/greet/child.go

package greet import "fmt" var Message = "Hey there. I am child." func init() {

fmt.Println("greet/greet/child.go ==> init()")

}

The main thing to remember is, an imported package is initialized only once per package. Hence if you have multiple import statements in a package, an imported package is going to be initialized only once in the lifetime of main package execution.