Go was designed to encourage good software engineering practices. One of the guiding principles of high-quality software is the DRY principle - Don’t Repeat Yourself, which basically means that you should never write the same code twice. You should reuse and build upon existing code as much as possible.

Functions are the most basic building blocks that allow code reuse. Packages are the next step into code reusability. They help you organize related Go source files together into a single unit, making them modular, reusable, and maintainable.

In this article, you’ll learn how to organize Go code into reusable packages, how to import a package, how to export a function, type, or variable to outside packages, and how to install 3rd party packages.

Let’s get started!

Go Workspace and Code Organization

Before learning about Go packages, It’s important to understand how Go code is organized inside the so-called Go workspace. Please check out the following article to brush up your concepts about Go code organization -

GOPATH, Go Workspace, and Go Code Organization

Go Package

In the most basic terms, A package is nothing but a directory inside your Go workspace containing one or more Go source files, or other Go packages.

Go Package Illustration

Every Go source file belongs to a package. To declare a source file to be part of a package, we use the following syntax -

package <packagename>

The above package declaration must be the first line of code in your Go source file. All the functions, types, and variables defined in your Go source file become part of the declared package.

You can choose to export a member defined in your package to outside packages, or keep them private to the same package. Other packages can import and reuse the functions or types that are exported from your package.

Let’s see an example - Almost all the code that we have seen so far in this tutorial series include the following line -

import "fmt"

fmt is a core library package that contains functionalities related to formatting and printing output or reading input from various I/O sources. It exports functions like Println(), Printf(), Scanf() etc, for other packages to reuse.

Packaging functionalities in this way has the following benefits -

  • It reduces naming conflicts. You can have the same function names in different packages. This keeps our function names short and concise.

  • It organizes related code together, so that it is easier to find the code you want to reuse.

  • It speeds up the compilation process by only requiring recompilation of smaller chunks of a program. Although we use the fmt package, we don’t need to recompile it every time we change our program.

The main package and main() function

Go programs start running in the main package. It is a special package that is used with programs that are meant to be executable.

By convention, Executable programs (the ones with the main package) are called Commands. Others are called simply Packages.

The main() function is a special function that is the entry point of an executable program. Let’s see an example of an executable program in Go -

// Package declaration
package main

// Importing packages
import (
	"fmt"
	"time"
	"math"
    "math/rand"
)

func main() {
	// Finding the Max of two numbers
	fmt.Println(math.Max(73.15, 92.46))
	
	// Calculate the square root of a number
	fmt.Println(math.Sqrt(225))

	// Printing the value of `𝜋`
	fmt.Println(math.Pi)

	// Epoch time in milliseconds
	epoch := time.Now().Unix()
	fmt.Println(epoch)

	// Generating a random integer between 0 to 100
	rand.Seed(epoch)
	fmt.Println(rand.Intn(100))
}
$ go run main.go
# Output
92.46
15
3.141592653589793
1538045386
40

Importing Packages

There are two ways to import packages in Go -

// Multiple import statements
import "fmt"
import "time"
import "math"
import "math/rand"
// Factored import statements
import (
	"fmt"
	"time"
	"math"
    "math/rand"
)

Go’s convention is that - the package name is the same as the last element of the import path. For example, the name of the package imported as math/rand is rand. It is imported with path math/rand because It is nested inside the math package as a subdirectory.

Exported vs Unexported names

Anything (variable, type, or function) that starts with a capital letter is exported, and visible outside the package.

Anything that does not start with a capital letter is not exported, and is visible only inside the same package.

When you import a package, you can only access to its exported names.

package main

import (
	"fmt"
	"math"
)

func main() {
	// MaxInt64 is an exported name
	fmt.Println("Max value of int64: ", int64(math.MaxInt64))

	// Phi is an exported name
	fmt.Println("Value of Phi (ϕ): ", math.Phi)

	// pi starts with a small letter, so it is not exported
	fmt.Println("Value of Pi (𝜋): ", math.pi)
}
# Output
./exported_names.go:16:38: cannot refer to unexported name math.pi
./exported_names.go:16:38: undefined: math.pi

To fix the above error, you need to change math.pi to math.Pi.

Creating and managing custom Packages

Until now, We have only written code in the package main and used functionalities imported from Go’s core library packages.

Let’s create a sample Go project that has multiple custom packages with a bunch of source code files and see how the same concept of package declaration, imports, and exports apply to custom packages as well.

Here is how our project is organized in the Go workspace -

Go custom package organization in Workspace

Let’s focus on the contents of the src folder. The bin and pkg folder contents will be auto-generated by the Go tool once we install our package.

Create a folder inside your go workspace -

mkdir -p $GOPATH/src/github.com/example

Let’s now look at the code inside each source files one by one.

numbers/prime.go

package numbers

import "math"

// Checks if a number is prime or not
func IsPrime(num int) bool {
    for i := 2; i <= int(math.Floor(math.Sqrt(float64(num)))); i++ {
        if num%i == 0 {
            return false
        }
	}
    return num > 1
}

strings/reverse.go

package strings

// Reverses a string
/*
	Since strings in Go are immutable, we first convert the string to a mutable array of runes ([]rune), 
	perform the reverse operation on that, and then re-cast to a string.
*/
func Reverse(s string) string {
	runes := []rune(s)
	reversedRunes := reverseRunes(runes)
	return string(reversedRunes)
}

strings/reverse_runes.go

package strings

// Reverses an array of runes
// This function is not exported (It is only visible inside the `strings` package)
func reverseRunes(r []rune) []rune {
	for i, j := 0, len(r)-1; i < j; i, j = i+1, j-1 {
		r[i], r[j] = r[j], r[i]
	}
	return r
}

strings/greeting/texts.go (Nested package)

// Nested Package
package greeting

// Exported
const  (
	WelcomeText = "Hello, World to Golang"
    MorningText = "Good Morning"
	EveningText = "Good Evening"
)

// Not exported (only visible inside the `greeting` package)
var loremIpsumText = `Lorem ipsum dolor sit amet, consectetur adipiscing elit, 
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad 
minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea 
commodo consequat.`

myapp/app.go (The main package: entry point of our program)

package main

import (
	"fmt"
	"github.com/callicoder/example/numbers"
	"github.com/callicoder/example/strings"	
	"github.com/callicoder/example/strings/greeting" // Importing a nested package
	str "strings"	// Package Alias
)

func main() {
	fmt.Println(numbers.IsPrime(19))

	fmt.Println(greeting.WelcomeText)

	fmt.Println(strings.Reverse("callicoder"))

	fmt.Println(str.Count("Go is Awesome. I love Go", "Go"))
}
# Installing the Go package
$ go install github.com/callicoder/example/myapp
# Running the executable binary
$ myapp
true
Hello, World to Golang
redocillac
2

Things to note

  • Import Paths

    All import paths are relative to the src directory of your Go workspace.

    import (
        "github.com/callicoder/example/numbers"
        "github.com/callicoder/example/strings" 
        "github.com/callicoder/example/strings/greeting"
    )
    
  • Package Alias

    You can use package alias to resolve conflicts between different packages of the same name, or just to give a short name to the imported package

    import (
        str "strings"   // Package Alias
    )
    
  • Nested Package

    You can nest a package inside another. It’s as simple as creating a subdirectory -

    src
        github.com/callicoder/example
            strings                     # Package
                greeting                # Nested Package
                    texts.go
    

    A nested package can be imported similar to a root package. Just provide its path relative to the $GOPATH/src directory -

    import (
        "github.com/callicoder/example/strings/greeting"
    )
    

Installing 3rd party Packages

You can use go get command to fetch 3rd party packages from remote repositories.

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

The above command fetches the gorm package from Github and stores it at the path src/github.com/jinzhu/gorm inside your Go workspace.

That’s it. You can now import and use the above package in your program like this -

import "github.com/jinzhu/gorm"

Note that, Unlike other languages and package management tools (npm, maven etc), Go doesn’t have a centralized official package repository. Therefore, it asks you to provide the hostname and path of the package in the go get command.

Here is another example of go get command that downloads a package from go.uber.org (Uber’s package hosting site for Go) -

$ go get -u go.uber.org/zap

The above command downloads and saves the zap package at the path $GOPATH/src/go.uber.org/zap. You can import it like this -

import "go.uber.org/zap"

Conclusion

That’s all folks! In this article, you learned how to organize Go code into reusable packages, how to import a package, how to export members of a package, how to create custom packages, and how to install third party packages.

You’ll learn about package initialization, blank identifier, and package documentation in a future article.

Next Article: Working with Arrays in Golang

Code Samples: github.com/callicoder/golang-tutorials