How Packages Work in Go

Jul 5, 2013


Since I started writing code in Go it has been a mystery to me how best to organize my code and use the package keyword. The package keyword is similar to using a namespace in C#, however the convention is to tie the package name to the directory structure.

Go has this web page that attempts to explain how to write Go Code.

http://golang.org/doc/code.html

When I started programming in Go this was one of the first documents I read. This went way over my head, mainly because I have been working in Visual Studio and code is packaged for you in Solution and Project files. Working out of a directory on the file system was a crazy thought.  Now I love the simplicity of it but it has taken quite a while for it all to make sense.

"How to Write Go Code" starts out with the concept of a Workspace. Think of this as the root directory for your project. If you were working in Visual Studio this is where the solution or project file would be located. Then from inside your Workspace you need to create a single sub-directory called src. This is mandatory if you want the Go tools to work properly. From within the src directory you have the freedom to organize your code the way you want. However you need to understand the conventions set forth by the Go team for packages and source code or you could be refactoring your code down the line.

On my machine I created a Workspace called Test and the required sub-directory called src. This is the first step in creating your project.




Then in LiteIDE open the Test directory (the Workspace), and create the following sub-directories and empty Go source code files.

First we create a sub-directory for the application we are creating. The name of the directory where the main function is located will be the name of the executable. In our case main.go contains the main function and is under the myprogram directory. This means our executable file will be called myprogram.

The other sub-directories inside of src will contain packages for our project. By convention the name of the directory should be the name of the package for those source code files that are located in that directory. In our case the new packages are called samplepkg and subpkg. The name of the source code files can be anything you like.


Create the same package folders and empty Go source code files to follow along.

If you don’t add the Workspace folder to the GOPATH we will have problems.

It took me a bit to realize that the Custom Directories window is a Text Box. So you can edit those folders directly. The System GOPATH is read only.

The Go designers have done several things when naming their packages and source code files. All the file names and directories are lowercase and they did not use underscores to break words apart in the package directory names. Also, the package names match the directory names. Code files within a directory belong to a package named after the directory.

Take a look at the Go source code directory for a few of the standard library packages:


The package directories for bufio and builtin are great examples of the directory naming convention. They could have been called buf_io and built_in.

Look again at the Go source code directory and review the names of the source code files.


Notice the use of the underscore in some of the file names. When the file contains test code or is specific to a particular platform, an underscore is used.

The usual convention is to name one of the source code files the same as the package name. In bufio this convention is followed. However, this is a loosely followed convention.


In the fmt package you will notice there is no source code file named fmt.go. I personally like naming my packages and source code files differently.

Last, open the doc.go, format.go, print.go and scan.go files. They are all declared to be in the fmt package.

Let’s take a look at the code for sample.go:

package samplepkg

import (
    "fmt"
)

type Sample struct {
    Name string
}

func New(name string) (sample * Sample) {
    return &Sample{
        Name: name,
    }
}

func (sample * Sample) Print() {
    fmt.Println("Sample Name:", sample.Name)
}

The code is useless but it will let us focus on the two important conventions. First, notice the name of the package is the same as the name of the sub-directory. Second, there is a function called New.

The function New is a Go convention for packages that create a core type or different types for use by the application developer. Look at how New is defined and implemented in log.go, bufio.go and cypto.go:

log.go
// New creates a new Logger. The out variable sets the
// destination to which log data will be written.
// The prefix appears at the beginning of each generated log line.
// The flag argument defines the logging properties.
func New(out io.Writer, prefix string, flag int) * Logger {
    return &Logger{out: out, prefix: prefix, flag: flag}
}

bufio.go
// NewReader returns a new Reader whose buffer has the default size.
func NewReader(rd io.Reader) * Reader {
    return NewReaderSize(rd, defaultBufSize)
}

crypto.go
// New returns a new hash.Hash calculating the given hash function. New panics
// if the hash function is not linked into the binary.
func (h Hash) New() hash.Hash {
    if h > 0 && h < maxHash {
        f := hashes[h]
        if f != nil {
            return f()
        }
    }
    panic("crypto: requested hash function is unavailable")
}

Since each package acts as a namespace, every package can have their own version of New. In bufio.go multiple types can be created, so there is no standalone New function. Here you will find functions like NewReader and NewWriter.

Look back at sample.go. In our code the core type is Sample, so our New function returns a reference to a Sample type. Then we added a member function to display the name we provided in New.

Now let’s look at the code for sub.go:

package subpkg

import (
    "fmt"
)

type Sub struct {
    Name string
}

func New(name string) (sub * Sub) {
    return &Sub{
        Name: name,
    }
}

func (sub * Sub) Print() {
    fmt.Println("Sub Name:", sub.Name)
}

The code is identical except we named our core type Sub. The package name matches the sub-directory name and New returns a reference to a Sub type.

Now that our packages are properly defined and coded we can use them.

Look at the code for main.go:

package main

import (
    "samplepkg"
    "samplepkg/subpkg"
)

func main() {
    sample := samplepkg.New("Test Sample Package")
    sample.Print()

    sub := subpkg.New("Test Sub Package")
    sub.Print()
}

Since our GOPATH points to the Workspace directory, in my case /Users/bill/Spaces/Test, our import references start from that point. Here we are referencing both packages based on the directory structure.

Next we call the New functions for each respective package and create variables of those core types.


Now build and run the program. You should see that an executable program was created called myprogram.

Once your program is ready for distribution you can run the install command.



The install command will create the bin and pkg folders in your Workspace. Notice the final executable was placed under the bin directory.

The compiled packages were placed under the pkg directory. Within that directory a sub-directory that describes the target architecture is created and that mirrors the source directories.

These compiled packages exist so the go tool can avoid recompiling the source code unnecessarily.

The problem with that last statement, found in the "How to Write Go Code" post, is that these .a files are ignore by the Go tool when performing future builds of your code. Without the source code files you can’t build your program. I have not found any documentation to really explain how these .a files can be used directly to build Go programs. If anyone can shed some light on this topic it would be greatly appreciated.

At the end of the day it is best to follow the conventions handled down from the Go designers. Looking at the source code they have written provides the best documentation for how to do things. Many of us are writing code for the community. If we all follow the same conventions we can be assured of compatibility and readability. When in doubt open finder to /usr/local/go/src/pkg and start digging.

As always I hope this helps you understand the Go programming language a little better.


Go Training

We have taught Go to thousands of developers all around the world since 2014. There is no other company that has been doing it longer and our material has proven to help jump start developers 6 to 12 months ahead of their knowledge of Go. We know what knowledge developers need in order to be productive and efficient when writing software in Go.

Our Go, Web and Data Science classes are perfect for both experienced and beginning engineers. We start every class from the beginning and get very detailed about the internals, mechanics, specification, guidelines, best practices and design philosophies. We cover a lot about "if performance matters" with a focus on mechanical sympathy, data oriented design, decoupling and writing production software.

Learn More

To learn about Corporate training events, options and special pricing please contact:

William Kennedy
ArdanLabs (www.ardanlabs.com)
bill@ardanlabs.com