Version 1.19 Refresh for Go Programmers
Been away from Go programming for a while? Let us look at stuff you may have forgotten.

I jump between numerous languages and when I come back to one I have not used in a while there are always things I have forgotten how to do. This story is a look at things which have a high probability of confusing you in modern Go. The primary reason is that these are things which have gone through numerous changes over the last couple of years. Environment variables are not used the same way anymore, and the build system has changed. Other things haven't changed, but are just easy to forget after a Go hiatus.
Here are some of the topics I like to cover:
GOROOTandGOPATHenvironment variables. Are they even needed anymore?Modules and packages
Command line tools—Building, fetching dependencies and running tests
Gotchas when using pointers
Logging
Error handling
Enums
Embedding data files in Go binaries
Difference Between GOROOT and GOPATH and Do We Need them Anymore?
For some reason, I always confuse Go environment variables with each other, and I keep forgetting whether I require them at all since modules got introduced.
GOROOT- Location of your Go SDK. Useful if you have multiple versions of Go installed.GOPATH- Root of your personal workspace. Defaults to~/goon Linux and macOS.
You can set these variables yourself. On my Mac, GOROOT is not set to anything and Go works fine. You don't need GOPATH to point to the directory where you develop your code. I put my Go code in ~/Dev/go, but you need a place to install third-party go tools and packages. That is where GOPATH comes in. I set my GOPATH to ~/.go in :
# Added to my ~/.config/fish/config.fish
set -x GOPATH $HOME/.goI put a dot in front to make it invisible (only works on Unix systems). The reason is that you rarely if ever need to go into the GOPATH directory, as you keep your own code in a separate directory. We can check the contents of the GOPATH directory, with the tree command.
❯ tree -L 1 $GOPATH
~/.go
├── bin
├── pkg
└── srcMake sure the path $GOPATH/bin is added to your PATH environment variable to ensure that your shell can locate Go commands. Here is how I configure my fish shell to find go binaries.
# To run home-made go commands and installed go programs
set -x PATH $PATH /usr/local/go/bin $GOPATH/binFor Bash and Zsh users out there, you will write something like this instead:
export PATH="$PATH:/usr/local/go/bin:$GOPATH/bin"Command Line Tools and Modules in Go v1.18
The introduction of modules in Go changed a lot about how we use command line tools and environmental variables in Go. It reduced the importance of environment variables such as GOPATH. Allow me to explain how with an example of how I build different parts of my rocket Go project. To create this project, I would type the following commands in my shell:
❯ cd $GOPATH/src
❯ mkdir rocket
❯ cd rocket
❯ go mod init github.com/ordovician/rocketOnce populated with source code files, the project directory will look like the following listing produced with the handy tree command:
❯ tree -L 2 rocket/
rocket/
├── README.md
├── go.mod
├── go.sum
├── cmd
│ ├── launcher
│ └── server
├── engine
│ ├── engine.go
│ ├── engine_example_test.go
│ ├── merlin_engine.go
│ └── rutherford_engine.go
├── math
│ └── math.go
├── physics
│ ├── equations.go
│ ├── motion.go
│ ├── rigidbody.go
│ └── rigidbody_test.go
├── propulsion.go
├── stagedrocket.go
├── stagedrocket_test.go
├── tank
│ ├── flexitank.go
│ ├── tank.go
│ └── tanks_test.go
├── types.go
└── util_test.goThe source code at the top level such as propulsion.go and stagedrocket.go belong to the rocket package. That is reflected in their location within the directory hierarchy as well as within the source code. The start of my stagedrocket.go source code file looks as follows:
package rocket
import (
. "github.com/ordovician/rocket/engine"
. "github.com/ordovician/rocket/physics"
. "github.com/ordovician/rocket/tank"
)
type MultiStaged struct {
payload Rocket
Propulsion
}Each subdirectory under the rocket top-level directory defines different packages. Here is from the start of the flexitank.go file:
package tank
import (
. "github.com/ordovician/rocket/physics"
)
// A tank with flexible size
type FlexiTank struct {
DryMass Kg
TotalMass Kg
propellant Kg
}There is an overlap between modules and packages. github.com/ordovician/rocket is a module containing the rocket package. But that is not the only package it contains. It also contains the physics, tank and math packages for instance.
Command Line Tools in cmd
The rocket/cmd directory is interesting because it contains files and directories to create runnable executables. A source code file can only be used to create an executable if the main function is defined within. However, you cannot have a mainfunction in any package. It needs to be defined within a package named main. That is why each command we are building needs a separate subdirectory under therocket/cmd directory. That way, we can define multiple main packages.
Building Packages and Command Line Tools
We can build whole packages, individual files, sub packages or command line tools with the go build command. When creating a module you specify a name for it which should reflect where you will put that module online. For instance, my rocket module is named github.com/ordovician/rocket with the go mod init command because that is the git repository where I will store the package. Because that is the actual name of your module, typically use that when building, running or testing. You would write:
❯ go build github.com/ordovician/rocketNote that this approach only works when you are in a directory with a go.mod file which actually defines this module. This build command doesn't build packages which are not part of the rocket package, such as the command line tools we have in the cmd subdirectory. To build a specific command, we have to specify the full package path. The same path you would use when importing a Go package:
❯ go build github.com/ordovician/rocket/cmd/serverOf course, it might be awkward to write the full path like this every time. Fortunately, we can use relative paths. You have to start with ./ because Julia will treat cmd/server or /cmd/server as a package name and not a locally defined package.
❯ go build . # build rocket package
❯ go build ./engine/ # build engine package
❯ go build ./cmd/server # build server binary
This approach also works for the test and run commands as well. Let us look at running tests defined in _test.go files (shortened output):
❯ go test -v ./tank
=== RUN TestMakeMediumTank
--- PASS: TestMakeMediumTank (0.00s)
=== RUN TestMakeLargeTank
--- PASS: TestMakeLargeTank (0.00s)
=== RUN ExampleMediumTank_Consume
--- PASS: ExampleMediumTank_Consume (0.00s)
PASS
ok github.com/ordovician/rocket/tank (cached)
❯ go test -v .
=== RUN TestStageSeparation
--- PASS: TestStageSeparation (0.00s)
=== RUN TestThrustTooLowForGravity
--- PASS: TestThrustTooLowForGravity (0.00s)
=== RUN TestPlentyPowerful
--- PASS: TestPlentyPowerful (0.00s)
=== RUN TestCompareWithNewtonMotionEquations
--- PASS: TestCompareWithNewtonMotionEquations (0.00s)
PASS
ok github.com/ordovician/rocket (cached)To run an individual test, add the -run flag.
❯ go test -v -run TestStageSeparation
=== RUN TestStageSeparation
--- PASS: TestStageSeparation (0.00s)
PASS
ok github.com/ordovician/rocket 0.116sWe can use the same approach to run tests within sub packages.
❯ go test -v -run TestMakeLargeTank ./tank
=== RUN TestMakeLargeTank
--- PASS: TestMakeLargeTank (0.00s)
PASS
ok github.com/ordovician/rocket/tank 0.164sWe can use the go run command in a similar fashion. If you are constantly changing and developing an executable, it is awkward to do a two-step process of first building and then running. Better to combine compiling and running into one step. Here I will show an example from my cryptools module which contains a number of little command for encryption and decryption. In this example, I am generating an encryption and decryption key which is 16 bytes long stored in base64 format.
❯ go run ./cmd/generate -keylen 16 -encoding base64 key.txtThis is equivalent to the following two-step process:
❯ go build ./cmd/generate
❯ ./generate -keylen 16 -encoding base64 key.txtInstalling Go Modules and Tools
In the past, you would have used the go get command to get any Go tools you wanted to install. In modern Go, we only use the go get command to install dependencies of a module you develop. So say you are developing a medical application which depends on the GTK GUI toolkit. First, I would create the project:
❯ mkdir medical
❯ cd medical
❯ go mod init github.com/ordovician/medicalNext, I add the GTK dependency to the go.mod file.
❯ go get github.com/gotk3/gotk3
go: downloading github.com/gotk3/gotk3 v0.6.1
go: added github.com/gotk3/gotk3 v0.6.1Notice how the go.mod file has been modified to include this new dependency:
❯ cat go.mod
module github.com/ordovician/medical
go 1.18
require github.com/gotk3/gotk3 v0.6.1 // indirectFor many older go projects, you will notice that the README file says you should write go get to install them, but that no longer applies. Instead, you use go installand specify with an @ symbol which version you want. Use @latest to get whatever is the latest. I am old fashion using the TextMate editor, which means I need to install gocode to get command completion in my editor. I do that like this:
❯ go install github.com/stamblerre/gocode@latestBut there are lots of other interesting Go tools you might want to install. Having a debugger is useful. Delve is the more popular one for Go at the moment:
❯ go install github.com/go-delve/delve/cmd/dlv@latestIf you want to install a specific version, you could write:
❯ go install github.com/go-delve/delve/cmd/dlv@v1.7.3Removing and Adding Needed Dependencies
You may want to remove or add dependencies depending on what your module actually uses. You can use go mod tidy for that purpose. It removes dependencies not used in any of your source code and actually adds dependencies you need. Find out more with:
❯ go help mod tidy

