Some Go basics to get you started, Part III: Modules, Packages and Libraries

Go uses the concept of modules and packages, where modules are new to Go 1.11. All Go files in a folder are part of a package, whether or not you like it, and this includes the main.go file. You will know this as every single Go file starts with package <name>. Everything in a package is visible to the rest of the package. At first, particularly if you come from an object-oriented language, this may seem like a messy issue. Turns out it isn’t.

First, let’s talk about packages

All your public types, interfaces and methods in your package are exported, so other packages can see them if they import your package. Since Go does not have the public, private, or protected keywords, or anything similar, it uses a different semantic to determine whether or not something is exported; the capitalisation of the first letter of the declaration. Sounds strange, but it is rather therapeutic as it uses far fewer keystrokes. Here’s an example:

package awesome

// MyAwesomeMethod is accessible outside of the package:
func MyMethod() {}

// This is "private", but still available in the awesome package:
func myHiddenMethod() {}

// MyVariable is accessible outside the package:
var MyVariable int

// This is private to the awesome package:
var someInternalVariable string

So how do you get access to these exposed package types from the awesome package? You import them in the source file that you wish to use them in. We import packages with the import keyword. Even a package in your own application (this means any Go code in sub folders) is imported the same way. Import statements have two flavours:

import "fmt"

Or:

import "github.com/spf13/cobra"

The two flavours are very specific. The first import is the format used when importing from the standard library. All standard library imports omit the import path. The second flavour is how all other code is imported, including code you have in your project. If there’s a Go file in a sub folder in your project, then you will need to import it to see its exported types, and you need to use the fully qualified import path. You should not import a package using relative paths, and they don’t work in a workspace. This keeps things explicit and consistent and you can reason about where the imported package comes from. You can often just add an https:// to the front of the import path and hit the site, assuming that you are in a repository like Github or BitBucket. What is also great to know, is that you can download a package with the same path as the import statement (mostly, there are exceptions, it’s always best to go to the site’s hosted source page):

go get github.com/spf13/cobra

The import path also uniquely identifies a package. If I create a new library called Cobra, there is no way it will clash with the original one, as I will need to own the full import path. The closest I could get is something like:

import "github.com/brianj-za/cobra"

It will be a different package. However, there’s one issue. The package name is the folder name so I call my package cobra, as is the original. This would cause a name clash if I imported them both. So, there’s another import option, and that is to use an alias:

import mycobra "github.com/brianj-za/cobra"

Now I can use the original cobra via the cobra package name, and my version through the mycobra package name. I could just as easily have aliased the other way around, it doesn’t matter. Here’s how I would call those two different package’s exported types:

package main

import (
	"github.com/spf13/cobra"
	mycobra "github.com/brianj-za/cobra"
)

func main() {
	cobra.SomePublicMethod()
	mycobra.SomePublicMethod()
}

There is one more variant of the alias, the blank identified _ alias:

import _ "github.com/brianj-za/cobra"

This one is weird at first. You have aliased away the import by using a blank identifier (I have not talked about these yet). Why would this even be useful? Well, packages can have an init method, and a package included this way will still have its init method called. You are importing a package only for its side effects. This design is useful for plugin style packages, and it is common when importing database plugins. You will rarely use a database plugin directly, but through its side effects and via the sql standard library. I will cover this in a later article.

Oh, nearly forgot, there’s one more alias variant:

import . "math"

Use of the period means that it aliases all the imported types in to your current source and you no longer use the package name. This is not recommended though as it quickly gets messy. I’ve never used this alternative.

So back to my awesome package, how would my import look? Here we go:

package main

import "bitbucket.com/brianj-za/package-test/awesome"

func main() {
	awesome.MyMethod()
}

If I tried to use awesome.myHiddenMethod() it would give me an error:

./main.go:6:2: cannot refer to unexported name awesome.myHiddenMethod
./main.go:6:2: undefined: awesome.myHiddenMethod

Types of Packages

You can have packages within your current project, or you can create standalone packages. Standalone packages have no main function, but they can use the init function. Standalone packages are known as shared packages, and it’s a good way to expose functionality in a reusable way. I will discuss these in a later article, but they follow the same principles as an application except you cannot run them like an application.

Now, Modules

Modules are a new concept in Go and preliminary support has been rolled in to Go 1.11 as an opt-in feature. Modules are a group of versioned packages in a unit. This keeps everything together. You can convert several existing dependency management tools to Go modules.

Unfortunately, I have had no opportunity to use modules yet, so read the Go wiki on them here. The wiki does a great job explaining Modules.