One of the most fundamental design principles in writing software is the DRY principle — Don’t Repeat Yourself. As software engineers, we should always avoid writing the same code again and again.
DRY teaches us to design our software such that it is built upon reusable and modular units of code. This helps us save time and reduce software maintenance costs in the long run.
In most programming languages, functions are the most basic unit of reusable code. However, as our software grows in size, it is good practice to group functions into separate logical units. In Go, this can be done easily through packages.
In this article, I will explain how you can work with Go packages. You will learn how to import a package, export from a package, set up your own Go package, and install third-party packages.
I assume you have some programming experience and you are familiar with the basic syntax of Go.
Let’s get started! 🏃
What is a Go package?
A Go package is a directory in your project workspace that houses one or more Go source files or other nested Go packages.
In Go, every source file must belong to a package. All Go source files must begin with a package declaration like below.
Every function, type, and variable of a source file belongs to its declared package and can be accessed by other source files within the same package.
Go source files that live in the same directory must belong to the same package. Although it is not necessary to name a package after its directory, it is a good convention that you should follow.
A simple example
Go comes with a handful set of built-in packages. One of them is the
fmt package that provides versatile I/O functionalities. To use this package, we can import it as follows:
When you import a package, you have access to the functions exported by the package. You can use these functions using the dot operator. One of such functions exported by
Println() which formats and writes data to the standard output.
The eagle-eyed readers may have noticed a benefit of this design pattern. Accessing an exported function via the dot operator helps to prevent naming conflicts. You can reuse function names across different packages, which helps to keep function names concise and meaningful!
In Go, there are two types of packages: an executable package and a utility package.
An executable package, as the name suggests, is a special package that holds an executable program for Go to compile and run.
On the other hand, a utility package contains reusable helpers that support the program in an executable package. The
fmt package of Go is an example of a utility package.
To create an executable package in Go, you need to meet two criteria:
- The name of the package must be
- It must contain a function called
main(), which serves as the entry point of the executable program.
Let’s look at an example of a simple source file
main.go in the
To execute a
main package, you can either use
go build to compile and then run the executable file manually, or use
go run to do it in one step.
$ go run main.go
In the example above, there is only one source file in the
main package. With more complex programs, there could be more than one source file in a
main package. To compile and run them, you need to point Go to the directory of the
$ go run /path/to/directory/of/main_package
Multiple and nested imports
In most cases, you would want to import multiple packages. There are two ways where you can do this in Go:
Personally, I prefer the second method because it looks much cleaner without the repeated
Sometimes, packages are designed to be the children of a parent package. This parent-child relation is logical in cases where the child package performs a specific task of the parent package.
For example, inside Go’s
math package, there is a nested
rand package that implements pseudo-random number generators. We can import the
rand package and use it as follows:
In Go’s lingo, the last element of an import path indicates the nested package that is imported into a source file.
Export from packages
Any functions, types, or variables with a capitalized name are exported and visible outside of a package.
On the other hand, anything that does not start with a capital letter is private, i.e. not accessible outside of the package.
Restricting the export of certain functionalities from a package is a good design practice. It enables you to “force” other developers to use your package through specific interfaces. This minimizes the misuse of a package and unintended errors.
Create Go packages
Enough of theory! Let’s try to create our own Go packages and use them with the concepts you have learned.
Open up your terminal and type in the command below to create a project directory. I’ll name it
pkg-tutorial . You can name it whatever you want.
$ mkdir pkg-tutorial
Then, we need to create a Go module. Module is Go’s new dependency management system introduced in version 1.11.
In simple words, a Go module is a collection of Go packages managed by a
go.mod file. Every Go module must have a path defined in
The path in
go.mod represents the path to the module, and it is also used as the import path for the packages in the module.
Do not panic if you do not understand what I just said. Go modules deserve a separate article. For now, you just need to type the following commands in your terminal.
$ cd pkg-tutorial
$ go mod init github.com/jseow5177/pkg-tutorial
github.com/jseow5177/pkg-tutorial is the module path. After you run the commands above, you should see a
go.mod file created in your workspace.
Next, we will create three utility packages:
greets nested in
texts. The entry point of our program is in
main.go which belongs to the
main package at the root of our workspace.
After adding a couple of source files, the project structure will look like this:
Once you are done, at the root of your project workspace, execute
go run main.go in your terminal. You should see the following output:
I love Go!
2 + 5 is 7
2 - 5 is -3
Here are a few things that you should take note of:
- All import path of packages is relative to the module path
- Nested packages are created as subdirectories. You import them as usual starting from the module path.
fmtis Go’s built-in package. Hence, you won’t be importing it from our module.
casing.gois not exported. Try calling it in
main.goand see what you get!
- I followed the convention where a package name is the same as the directory name. However, this is not needed for the
Pretty simple right?
Install third-party packages
Go’s built-in packages provide a wide range of functionalities, but they may not be sufficient for your project.
In most cases, you would require packages written by other developers in the open-source community. Fortunately, Go has made it easy for you to install third-party packages into your project.
To install it, you use the
go get command and provide that path to the gorilla/csrf package.
go get github.com/gorilla/csrf
If you haven’t noticed, the module path above is the link to the package’s GitHub repository!
After you run the command, Go will fetch the package from GitHub into your local project. The
go.mod file will be updated accordingly to make gorilla/csrf a project dependency. You can then import it as follows:
That is all for packages with Go! In this article, you have learned about Go packages and how you can use them to organize your code in a Go project.
As the size of a project grows, Go packages can keep our code organized, modular, and reusable. Always remember to stay DRY.
Thank you for reading. Peace! ✌️