Road to Go Pro — Packages & Modules
By: Song Xue
Tags: Golang Development, Go, Golang, Golang Tutorial

A few words before we start. You can find the code used in this tutorial in this repository. You can find the full contents of Road to Go Pro here. If you missed the last one, you can find it via this link.

Thank you for following along the Road to Go Pro. We have learned the basics of Go in the previous tutorials. In this one, we are going to explore packages and modules. When you start to build Go projects, this knowledge will come in handy. Before we begin, I highly recommend checking out the example project in this repository (Part 05). Let’s get started.

Packages

Photo by Brandable Box on Unsplash

You might have heard of the concept, package, in many programming languages, such as Java, C# and python. In general, a package is a unit of reusable and portable code. In most of the Go projects, you can find both external packages (from third-party modules) and internal packages. The benefits of using packages are:

Using external packages helps to save a lot of time to build the functionalities we want from scratch. “Don’t reinvent the wheel.” Grouping related code into internal packages makes the codebase more readable and maintainable. When recompiling code in a package, it will only recompile what’s changed.

Now, let’s see how we can declare packages in Go.

Declaration

To create a package, we need to create three things:

A package directory in the project. In the example project, you can see a directory called stringset inside pkg directory. Note that this package directory is not placed directly under the root. And that is totally fine. You can put it anywhere you want. By convention, internal packages are placed under the pkg directory. At least one Go file in the package directory. In the example project, you can see there are two files under the stringset directory. package declarations in the files. In the files under stringset directory, you can find they all start with a package declaration: package stringset .

With these three things, a package is created in Go. Easy enough. However, a package with only these three things is not very useful. When adding stuff like functions and structs to the package, we need to consider whether they need to be exported.

Export

We discussed “Variable scope” in the previous tutorial. At that time, we briefly touched on accessibilities. In this section, we’re going to look at this topic again.

There is no export keyword in Go. Which element gets exported to the “outside world” is determined by its name. If the name starts with a capital letter, then it is exported, and vice versa: a name starting with a lowercase is not exported. In the example project, you can see functions like NewUniqueStringSet , Add , Remove and Format are exported. Therefore, they can be used in the main.go (outside of the stringset package).

Anything declared in a package can be exported. Whereas, we need to keep an eye on the “dependencies” of exported elements. For example, if we don’t export the struct UniqueStringSet from the stringset package, one of its exported function NewUniqueStringSet() will return an unexported struct, which introduces inconvenience to the consumers. Therefore, it’s better to export the dependencies, such as the structs used in the signatures of exported functions.

Import

Import is the opposite of export. As the consumers of a package, we need to import it so we can use it. We have been using import since the second tutorial (i.e. import "fmt" ). When importing packages, we need to specify the package names in one single import statement. If you need to import more than one package, the package names need to be wrapped with brackets (no commas in between packages). Optionally, you can declare aliases for package names. Usually, we use aliases for packages with long names so that referencing them becomes easier and cleaner.

Import packages

Init function

init is a special function in Go. A package can be imported many times but its init function only runs once during the lifetime of the application. Hence, init function is widely used for initialisation purposes. It is not a mandatory function in the package, it can be omitted it if you don’t need it.

In the example package ( stringset ), I used init function to initialise a string processor which is used later in the package.

init function example

Documentation

Documentation is one of the essential components in the package, especially when the package is consumed by external parties. Go provides a tool, godoc , to facilitate HTML documentation generation from comments. You can check out what Go documents look like from GoDoc website. It hosts generated documentation for public packages.

godoc doesn’t take all of the comments when generating documentation. It fetches only the comments placed on top of declarations. Those comments should start with the declared names. There is one exception, for packages, the comments should start with Package <Package_Name> .

godoc comment example

The comments in the example above are gathered by godoc and it generates beautiful HTML documentation (shown below).

godoc HTML documentation example

If you want to provide a detailed overview of the package, you might find adding long comments on top of the package declaration looks messy. You can solve this problem in an elegant way by creating a doc.go file. This is a dedicated file for documentation. You can add as many details as you want in it. By the way, multi-line comment symbol in Go is /* ... */ .

dedicated doc.go example

The contents in doc.go will show up in the overview section of the HTML documentation.

godoc HTML overview example

To generate the HTML documentation, you need to run the godoc command-line tool. Running godoc , by default, starts a documentation server at port 6060 . You can find more details about this CLI here. And in the README file of the example project, you will find a section about godoc , which shows you how you can navigate to the stringset package documentation.

Modules

Photo by Caleb Angel on Unsplash

Go introduced its new dependency management system, Go Modules, in version 1.11. In the old days, you needed to place your code and its dependencies under $GOPATH for Go to work properly. This was quite annoying and counter-intuitive. At the time of writing, the latest stable version of Go is 1.15. And fortunately, starting from 1.13, Go module becomes the default mode of Go development. Now you can place your code anywhere and use modules to manage the dependencies easily.

A Go module is a collection of packages stored in a directory with a go.mod file. A go.mod file, which is very similar to a package.json file in Javascript world, stores the information about the dependencies used in the current project.

To initialise a new Go module, you can simply run

“go mod init” command

There are no special rules about how you should name a module but usually, the module name starts with an organisation name and then followed by the module name. For instance, if a module is hosted on Github, then the full module name should look something like github.com/go-chi/chi .

Once you have run the go mod init command, you should see a new go.mod file created under the root directory of the project. In that file, you should see the module name you declared and the current go version you’re using.

go.sum file example

Dependencies

Dependencies used in Go modules can be categorised into two kinds, external dependencies and internal packages respectively.

External dependencies

External dependencies are established modules which you can add to your module and start using them directly. Adding external dependencies is very straightforward. You run the go get command to add desired dependencies to your module.

adding dependency

After running this, you will find a new line in the go.mod file.

an external dependency in go.mod file

This line records the version and module name of the dependency you just added. Note that the indirect comment means you’re not using the dependency in any source files at the moment. Once you start to use it, the indirect comment will disappear after running go mod tidy in the terminal.

If you want to upgrade the version of your dependencies, you need to add -u flag in the go get command.

upgrading dependency

Go module also has a vendor mode. If it is turned on, Go saves a copy of the source files of your dependencies. You can choose to commit them in the repository. This way, the dependencies are consistent for anyone who uses the repository. To enable vendor mode, you can run this command.

turning on vendor mode

You will find a new directory called vendor created under the root directory. The source files of your dependencies are stored there. When Go compiler detects there’s a vendor folder in the module, it will use that before checking your $GOPATH .

Internal packages

Creating a package for a unit of work is very common when developing Go modules. For the packages you create inside the module, you can reference it without running the go get command.

To import an internal package, you need to add <module_name>/<path_to_package> to the import statement. For instance, in the example project, you can import the stringset package by referencing github.com/songx23/RoadToGoPro/part05/pkg/stringset in the import statement.

What’s next?

Photo by Olesya Grichina on Unsplash

This is the last piece of Road to Go Pro… in 2020. Coming up in 2021, we will talk about other exciting topics such as concurrency, how to build an API and etc. So stay tuned.

Merry Christmas & Happy New Year! Wish you all have a nice holiday.

Please feel free to leave a comment below if you run into any problems or if you need a helping hand. Feedback is always welcome. Thank you for reading!

A special thanks to Mark Hume-Cook for reviewing this tutorial.