Everything you need to know about Packages in Go

A complete overview of package management and deployment in Go programming language

Everything you need to know about Packages in Go

⚠️   Migrate to Go Modules

Go Modules are the modern way to manage Go projects and dependencies. If you are starting a new project, use modules instead of the older GOPATH workflow. Read more about Go Modules.


If you have worked with Java or JavaScript on Node.js, the idea of a package will feel familiar. A package is a directory of code that groups related behavior and exposes selected values from one place.

Imagine you have many helper functions that you use across projects. Some functions work with strings, such as toUpperCase and toLowerCase, so you put them in a file like case.go. Other string helpers can live in other files.

Once the helpers grow, you group related files inside directories. That parent directory becomes the package.

package-name
├── string
|  ├── case.go
|  ├── trim.go
|  └── misc.go
└── number
   ├── arithmetics.go
   └── primes.go

We will look at imports and exported values step by step. For now, think of a package as a directory containing .go files.

Every Go program belongs to a package. As we discussed in Getting started with Go, a standalone executable program uses package main. When you install a main package, Go creates a binary. When you install a non-main package, Go creates a package archive.

Let’s create an executable package. As we know, to create a binary executable file, we need our program to be a part of main package and it must have main function which is the entry point of execution.

// src/app/entry.go
package main

import "fmt"

func main() {
  fmt.Println("app/entry.go ==> main()")
}
$ go install app
$ app
app/entry.go ==> main()

A package name usually comes from the directory inside src. In this example, app is the package because app is a child directory of src. So go install app looks for $GOPATH/src/app.

Then it compiled the package and created app binary executable file (same as the package name) inside bin directory (set by GOBIN) which should be executable from the terminal since bin directory in the PATH.

💡 The package declaration, such as package main, can be different from the directory name. When you import a package, Go uses the package declaration as the reference name in code.

go install <package> command looks for any file with main package declaration inside given package directory. If it finds a file, then Go knows this is an executable program and it needs to create a binary file. A package can have many files but only one file with main function, since that file will be the entry point of the execution.

If a package does not contain a file with main package declaration, then Go creates a package archive (.a) file inside pkg directory.

// src/app/entry.go
package app

import "fmt"

func main() {
  fmt.Println("app/entry.go ==> main()")
}
$ go install app
pkg/
└── darwin_amd64/
   └── app.a

Since app is not an executable package, it created app.a file inside pkg directory. We cannot execute this file as it’s not a binary executable file.

Package naming convention

Go community recommends to use plain and simple names for packages. For example, strutils for string utility functions or http for HTTP requests related functions. A package names with under_scores, hy-phens or mixedCaps should be avoided.

Creating a package

As we discussed, there are two types of packages. An executable package and a utility package. An executable package is your main application since you will be running it. A utility package is not self-executable, instead, it enhances the functionality of an executable package by providing utility functions and other important assets.

As we know, a package is nothing but a directory. Let’s create greet directory inside src and put some files in it. This time, we will write package greet a declaration on the top of each file to state that this is a utility package.

// src/greet/day.go
package greet

Export members

A utility package is supposed to provide some variables to a package who imports it. Like export syntax in JavaScript, Go exports a variable if a variable name starts with Uppercase. All other variables not starting with an uppercase letter is private to the package.

⚠️ I am going to use variable word from now on in this article, to describe an export member but export members can be of any type like constant, map, function, struct, array, slice etc.

Let’s export a greeting variable from day.go file.

// src/greet/day.go
package greet

var morning = "Good Morning"
var Morning = "Hey, " + morning

In this program, Morning variable will be exported from the package but not the morning variable since it starts with a lowercase letter.

Importing a package

Now, we need an executable package which will consume our greet package. Let’s create an app directory inside src and create entry.go file with main package declaration and main function. Note here, Go packages do not have an entry file naming system like index.js in Node. For an executable package, a file with main function is the entry file for execution.

To import a package, we use import syntax followed by the package name.

💡 Unlike other programming languages, a package name can also be a subpath like some-dir/greet and Go will automatically resolve the path to the greet package for us as you will see in the nested package topic ahead.

Go first searches for package directory inside GOROOT/src directory and if it doesn’t find the package, then it looks for GOPATH/src. Since fmt package is part of Go’s standard library which is located in GOROOT/src, it is imported from there. But since Go cannot find greet package inside GOROOT, it will lookup inside GOPATH/src and we have it there.

// src/app/entry.go
package main

import "fmt"
import "greet"

func main() {
  fmt.Println(greet.morning)
}
$ go run src/app/entry.go
# command-line-arguments
src/app/entry.go:7:14: cannot refer to unexported name greet.morning
src/app/entry.go:7:14: undefined: greet.morning

This program fails because morning is not exported from the greet package. We use dot notation to access exported package members, such as greet.Morning. The reference name greet comes from the package greet declaration.

// src/app/entry.go
package main

import (
  "fmt"
  "greet"
)

func main() {
  fmt.Println(greet.Morning)
}
$ go run src/app/entry.go
Hey, Good Morning

We can group fmt and greet package imports together using grouping syntax (parentheses). This time, our program will compile just fine, because Morning variable is exported from the greet package.

💡 You can also run an executable Go package with go run <package-name>. Go resolves the package from GOROOT or GOPATH and executes its main function.

Nested package

We can nest a package inside a package. Since for Go, a package is just a directory, it’s like creating a subdirectory inside an already existing package. All we have to do is write an appropriate package declaration and provide a relative path of the nested package when we import it.

// src/greet/de/day.go
package de

var morning = "Guten Morgen"
var Morning = "Hallo, " + morning
// src/app/entry.go
package main

import (
  "fmt"
  "greet/de"
)

func main() {
  fmt.Println(de.Morning)
}
$ go run src/app/entry.go
Hallo, Guten Morgen

As you can see from this example, Go has created the de variable from the greet/de package import which contains all the exports from the de sub-package. The de variable name comes from the package de declaration.

Package compilation

As discussed in the previous lesson, go run command compiles and executes a program. We know, go install command compiles a package and creates a binary executable file or package archive file.

The package archive file is created to avoid compilation of the package every single time it is imported in a program. The go install command pre-compiles a package and Go refers to .a files.

💡 Generally, when you install a third-party package, Go compiles the package and create package archive file. If you have created a package locally, then your IDE might create package archive as soon as you save the file in the package or when package gets modified. VSCode compiles the package when you save it if you have Go plugin installed.

$ go install greet
pkg/
└── darwin_amd64/
   └── greet.a

However, the nested packages won’t be compiled into archive files when you use go install <package-name> command. To create an archive file for the nested package, you need to use the same command but will the nested package path like go install greet/de in the previous example.

$ go install greet/de
pkg/
└── darwin_amd64/
   └── greet/
      └── de.a

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.

// src/app/version.go
package main

var version = "1.0.0"
// src/app/entry.go
package main

import "fmt"

func main() {
  fmt.Println("version ===>", version)
}
$ go run src/app/*.go
version ===> 1.0.0

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 this program would have failed to compile.

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

// src/app/entry.go
package main

import "fmt"

func main() {
  version := "1.0.1"
  fmt.Println("version ===>", version)
}
$ go run src/app/*.go
version ===> 1.0.1

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.

// src/app/entry.go
package main

import "fmt"

func main() {
  var a int = b
  var b int = c
  var c int = 2

  fmt.Println(a, b, c)
}
$ go run src/app/entry.go
# command-line-arguments
src/app/entry.go:6:14: undefined: b
src/app/entry.go:7:14: undefined: c

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.

// src/app/entry.go
package main

import "fmt"

var a int = b
var b int = c
var c int = 2

func main() {
  fmt.Println(a, b, c)
}
$ go run src/app/entry.go
2 2 2

In this 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.

// src/app/entry.go
package main

import "fmt"

var (
  a int = b
  b int = f()
  c int = 1
)

func f() int {
  return c + 1
}

func main() {
  fmt.Println(a, b, c)
}
$ go run src/app/entry.go
2 2 1

In this 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.

// src/app/entry.go
package main

import "fmt"

var (
  a int = b
  b int = c
  c int = a
)

func main() {
  fmt.Println(a, b, c)
}
$ go run src/app/entry.go
# command-line-arguments
src/app/entry.go:6:5: initialization loop:
  a refers to
  b refers to
  c refers to
  a

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

// src/app/f.go
package main

func f() int {
  return c + 1
}
// src/app/entry.go
package main

import "fmt"

var (
  a int = b
  b int = f()
  c int = 1
)

func main() {
  fmt.Println(a, b, c)
}
$ go run src/app/*.go
2 2 1

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, so you cannot 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.

// src/app/entry.go
package main

import "fmt"

func init() {
  fmt.Println("app/entry.go ==> init() [1]")
}

func init() {
  fmt.Println("app/entry.go ==> init() [2]")
}

func main() {
  fmt.Println("app/entry.go ==> main()")
}
$ go run src/app/*.go
app/entry.go ==> init() [1]
app/entry.go ==> init() [2]
app/entry.go ==> main()

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

// src/app/a.go
package main

import "fmt"

func init() {
  fmt.Println("app/a.go ==> init()")
}
$ go run src/app/*.go
app/a.go ==> init()
app/entry.go ==> init() [1]
app/entry.go ==> init() [2]
app/z.go ==> init()
app/entry.go ==> main()

After all the init functions are executed, Go calls main. The main use of init is to initialize package-level values that cannot be initialized directly in package scope.

// src/app/entry.go
package main

import "fmt"

var integers [10]int

func init() {
  fmt.Println("app/entry.go ==> init()")

  for i := 0; i < 10; i++ {
    integers[i] = i
  }
}

func main() {
  fmt.Println("app/entry.go ==> main()")
  fmt.Println(integers)
}
$ go run src/app/*.go
app/entry.go ==> init()
app/entry.go ==> main()
[0 1 2 3 4 5 6 7 8 9]

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."
// src/app/entry.go
package main

import (
  "fmt"
  "greet"
  "greet/greet"
)

func main() {
  fmt.Println(greet.Message)
}
$ go run src/app/*.go
# command-line-arguments
src/app/entry.go:6:2: greet redeclared as imported package name
  previous declaration at src/app/entry.go:5:2

So, 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.

// src/app/entry.go
package main

import (
  "fmt"
  _ "greet"
  child "greet/greet"
)

func main() {
  fmt.Println(child.Message)
}
$ go run src/app/*.go
Hey there. I am child.

In this 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()")
}
// src/app/entry.go
package main

import (
  "fmt"
  _ "greet"
  child "greet/greet"
)

func init() {
  fmt.Println("app/entry.go ==> init()")
}

func main() {
  fmt.Println(child.Message)
}
$ go run src/app/*.go
greet/parent.go ==> init()
greet/greet/child.go ==> init()
app/entry.go ==> init()
Hey there. I am child.

The main thing to remember is, an imported package is initialized only once per package. So 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.

💡 If we use . as an alias, such as import . "greet/greet", all exported members of the package become available in the local file scope. That means fmt.Println(Message) would work without the child qualifier. This is called a dot import, and the Go community is not very fond of it because it can make code harder to read.

Program execution order

So far, we understood everything there is about packages. Now, let’s combine our understanding of how a program initializes in Go.

go run *.go
├── Main package is executed
├── All imported packages are initialized
|  ├── All imported packages are initialized (recursive definition)
|  ├── All global variables are initialized
|  └── init functions are called in lexical file name order
└── Main package is initialized
   ├── All global variables are initialized
   └── init functions are called in lexical file name order

Here is a small example to prove it.

// version/get-version.go
package version
import "fmt"
func init() {
 fmt.Println("version/get-version.go ==> init()")
}
func getVersion() string {
 fmt.Println("version/get-version.go ==> getVersion()")
 return "1.0.0"
}

/***************************/

// version/entry.go
package version
import "fmt"
func init() {
 fmt.Println("version/entry.go ==> init()")
}
var Version = getLocalVersion()
func getLocalVersion() string {
 fmt.Println("version/entry.go ==> getLocalVersion()")
 return getVersion()
}

/***************************/

// app/fetch-version.go
package main
import (
 "fmt"
 "version"
)
func init() {
 fmt.Println("app/fetch-version.go ==> init()")
}
func fetchVersion() string {
 fmt.Println("app/fetch-version.go ==> fetchVersion()")
 return version.Version
}

/***************************/

// app/entry.go
package main
import "fmt"
func init() {
 fmt.Println("app/entry.go ==> init()")
}
var myVersion = fetchVersion()
func main() {
 fmt.Println("app/fetch-version.go ==> fetchVersion()")
 fmt.Println("version ===> ", myVersion)
}
// src/app/entry.go
package main

import "fmt"

func init() {
  fmt.Println("app/entry.go ==> init()")
}

var myVersion = fetchVersion()

func main() {
  fmt.Println("app/entry.go ==> main()", myVersion)
}
$ go run app
version/entry.go ==> getLocalVersion()
version/get-version.go ==> getVersion()
version/entry.go ==> init()
version/get-version.go ==> init()
app/fetch-version.go ==> init()
app/entry.go ==> init()
app/fetch-version.go ==> fetchVersion()
app/entry.go ==> main() 1.0.0

Installing third-party package

Installing a third-party package is nothing but cloning the remote code into a local src/<package-name> directory. Unfortunately, Go does not support package version or provide package manager but a proposal is waiting here.

Go does not have a centralize official package registry, it asks you to provide hostname and path to the package.

$ go get -u github.com/jinzhu/gorm

Above command imports files from http://github.com/jinzhu/gorm URL and saves it inside src/``github``.com/jinzhu/gorm directory. As discussed in nested packages, you can import gorm package like below.

package main
import "github.com/jinzhu/gorm"
// use ==> gorm.SomeExportedMember

So, if you made a package and want people to use it, just publish it on GitHub and you are good to go. If your package is executable, people can use it as a command-line tool else they can import it in a program and use it as a utility module. The only thing they need to do is use this command.

$ go get github.com/your-username/repo-name

Migrate to Go Modules

Go Modules are the modern way to manage Go projects and dependencies. If you are working with older GOPATH packages, consider moving the project to modules. Read more about Go Modules.

#golang #packages