The Developer’s Cry

a blog about computer programming

Go Namespace Explainer

Go is a very capable language, and I feel it’s being overlooked due to its simplicity; people seem to naturally gravitate towards complexity (looking at you, Rust) even when it’s more practical to take something that simply works. That said, Go does have its quirks, which may be attributed to its designers coming from Plan9. Plan9 was a research operating system by Bell Labs, best described as “UNIX-but-different”. The quirks of Go give off a weird first impression. One you get past that however, you’ll find that there’s a lot to love about this language.

Newcomers to Go are sure to stumble over the namespacing. How to structure a large Go code? Coming from C myself, I got so confused about how to use imports in Go. Well, the first thing to do is forget whatever you think to know about namespacing—Go is not C, Go is not Java, Go is not any of those other languages you happened to have programmed in. Go in with an open mind, it’s not complicated, just different.

Key in understanding the namespacing of Go is getting the terminology right.

Source files are part of a package; all the files a directory must share the same package name, and all the symbols in the same package are in the same namespace. This means that all symbols (exported or not) are visible and accessible across source files in the same package or directory. They are in the same namespace.

Programs must use the special package main. The compiler uses main.main() as entrypoint of a program.

A project that splits off some code into a separate package, puts those source files into a subdirectory. Again, all the files in a directory must share the same package name, they form a new namespace. By convention, the subdirectory bears the name of the package. (It is possible to name them differently … but don’t do this, as it is super confusing).

Projects that consist of multiple packages (ie. have multiple directories) must be managed as a module. A Go module is a collection of packages. The tooling around modules also take care of managing dependencies.

A good way of starting any new Go project, is to immediately initialize it as a module. Doing so prevents any confusion down the line.

$ mkdir ~/src/myproject
$ cd ~/src/myproject
$ go mod init gitlab.com/joe/myproject

But wait a minute … the code is not public and it’s not published yet. The module can be named anything you like, but if the code is going to be published, then the module should use the URL where the code can be downloaded in the future. Even if you’re never going to publish this code, then it’s customary to use an internet domain to make it a unique name, and to indicate where this project originated from.

But what if I’m developing a program and not a downloadable module? It doesn’t matter … in Go’s terminology, both are a “module”, as a module is just a collection of packages.

Now, let’s see what an example project would look like.

src/greetr/
  go.mod               gitlab.com/joe/greetr
  main.go              package main
  startup.go           package main
  exit.go              package main
src/greetr/netcode
  server.go            package netcode
  client.go            package netcode

The following is an example of how to use the subpackage:

/*
    greetr main.go
*/

package main

import (
    "gitlab.com/joe/greetr/netcode"
)

func main() {
    startupCode()            // lives in startup.go

    netcode.StartServer()    // in netcode/server.go

    cleanupBeforeExit()      // lives in exit.go
}

The import line is perhaps the most funny thing in here. It is composed of the module name, which looks like a URL, and the directory “netcode” in the filesystem. The “netcode” symbol used to call StartServer() is the package name however (!). Remember what I said earlier about naming package directories.

The package can be imported under a different identifier like so:

package main

import (
    nc "gitlab.com/joe/greetr/netcode"
)

func main() {
    startupCode()

    nc.StartServer()

    cleanupBeforeExit()
}

Now suppose we wish to publish the netcode as its own thing, so that others may use that code as well. Then we should put it in its own project root, and the project structure would be like this:

src/netcode/
  go.mod               gitlab.com/joe/netcode
  server.go            package netcode
  client.go            package netcode
src/netcode/netcode_test/
  netcode_test.go      package netcode_test
src/netcode/example/
  example.go           package main

Notice how the main package is now in a subdirectory.

Lastly worth mentioning is that foreign modules are dependencies, that have to be managed. So when building a Go code, run the housekeeping command:

$ go mod tidy

The tidy command may put a go.sum file in the directory, containing module checksums. The go.mod and go.sum files should be committed into git (ie. be put under version control) for sake of correctness and safety.

That’s all there is to know about Go namespaces for now. Seems simple enough, eh? People, myself included, tend to get confused because of what they’ve been ingrained with in other languages.