Go Observations
A Julia developer returns to Golang and is impressed by the simplicity and speed of the Go toolchain.

I did a bunch of Go programming the past and even held conference talks on Go. But I have been away from Go for a while and did both Swift and Julia programming in the meantime.
Fortunately, Go has remained remarkably stable as a language since I first picked it up, so I don’t have to learn a lot of new things. Rather, it is more of a refresher. The key new thing for me to learn are Go modules as well as familiarizing myself with the tooling around the language. I feel this is where most development has happened.
Here are a few observations of things that are easy to forget but that are important to remember.
You Don’t Need Constructors Most of the Time
I noticed that I had gotten into the habit of adding constructor functions. This is unnecessary and just inflexible in Go.
Go does not have constructors in the language per se but there are conventions for making functions with the New
prefix for making objects. This is how I initially made a constructor for a RigidBody
. If you’re wondering what that is, it is basically something from physics to simulate how an object that is not elastic moves. You can think of it as a solid steel ball.
type RigidBody struct {
Elevation float64
Velocity float64
Force Newton
Mass Kg
}
func NewRigidBody(mass Kg, force Newton) *RigidBody {
body := RigidBody{0, 0, force, mass}
return &body
}
This, however, is totally pointless, because Go automatically initializes fields you don’t specify to zero. All you need to do is use named fields like this:
body := RigidBody{Mass: 1, Force: 4}
In this case, the Velocity
and Elevation
fields are set to zero, which is exactly what I wanted. Here are some other variations:
RigidBody{Mass: 12, Force: 2}
RigidBody{Mass: 0.01, Force: 2, Velocity: 200}
RigidBody{
Mass: 5,
Force: 0.4,
Elevation: -250
}
In other words, most of the time you don’t have to bother with constructor functions in Go. This is a lesson I keep having to relearn in Go. It is a language that has really been engineered to keep things as simple as possible and avoid layering and unnecessary abstractions.
Main function goes into the main package
This is an example of the source code file that contains the main()
function. You cannot put main functions in any other package but the main
package.
package main
import (
"fmt"
"rockets/parts"
)
func main() {
tank := parts.NewMediumTank()
fmt.Printf("Tank size: %v\n", tank.Mass())
}
You Nest Multiple Packages Within a Module
The relation between packages and modules confused me a bit initially. Does the directory where I store my module have to be named the same thing as the module?
Nope, it doesn’t. Locally, I have my module in a directory called GoRockets
. The project structure looks like this:
GoRockets
├── README.md
├── go.mod
├── go.sum
├── playground.go
├── parts
│ ├── enginecluster.go
│ ├── largetank.go
│ ├── mediumtank.go
│ ├── rutherford.go
│ ├── tanks_test.go
│ └── types.go
├── physics
│ ├── motion.go
│ ├── rigidbody.go
│ └── rigidbody_test.go
└── vehicles
├── rocket.go
├── spaceship.go
└── types.go
The start of my enginecluster.go
file looks like this:
package parts
import . "github.com/ordovician/rockets/physics"
type EngineCluster struct {
Engine
count uint8
}
func (cluster *EngineCluster) Mass() Kg {
return cluster.Engine.Mass() * Kg(cluster.count)
}
As you can see, I import the physics
package. This is needed for my Mass
method which uses the Kg
unit defined in the physics
package. However, this does not compute. At the time of this writing there is no github repository at github.com/ordovician/rockets/physics
.
At first I thought I would have to write something like GoRockets/physics
. But because we want to store this package eventually on Github and don’t want to have to go through and change all the source code, Go allows you to use placeholders. The magic happens in the go.mod
file that defines my Go module. It looks like this:
module rockets
go 1.15
replace github.com/ordovician/rockets => ./
require github.com/ordovician/rockets v0.0.0-00010101000000-000000000000
The replace
statement makes github.com/ordovician/rockets
a placeholder for the current directory ./
where the go.mod
file is located.
Of course, you can use this to make placeholders for any number of modules you develop locally. That way, your source code can look as if you have already put all these modules online. Once the packages have been pushed online, you can simply change the go.mod
file.
One Package Per Directory, Except When…
In general, you can only have one package inside each directory or Go becomes unhappy with you. If you need another package, you can make a sibling or child subdirectory with the other packages.
There is an exception, however: files with names such as tanks_test.go
or _foobar.go
are not made part of the package. Files with names ending in _test.go
are used when you run the command:
$ go test -v
You can run this command inside any package directory. Hence you can easily run just a subset of package tests. You can even use this to build a package separately. For example, to build only the physics
package:
$ cd physics
$ go build
Running Individual Tests
Go allows you to run an individual test function. Here is a simple test file called tanks_test.go
:
package parts
import "testing"
func TestMakeMediumTank(t *testing.T) {
tank := NewMediumTank()
if tank.Mass() != mediumTotalMass {
t.Errorf("tank.Mass() = %f; want %f", tank.Mass(), mediumTotalMass)
}
}
func TestMakeLargeTank(t *testing.T) {
tank := NewLargeTank()
if tank.Mass() != largeTotalMass {
t.Errorf("tank.Mass() = %f; want %f", tank.Mass(), largeTotalMass)
}
}
I looked up how to run an individual test and I wrote something akin to:
$ go test -run Medium
PASS
ok rockets/parts 0.244s
But then I am like: “Did this even work?” I cannot tell if only one function was run or not. The trick here is to add the -v
switch, which lists tests run:
$ go test -v -run Medium
=== RUN TestMakeMediumTank
--- PASS: TestMakeMediumTank (0.00s)
PASS
ok rockets/parts 0.182s
Let us compare that with not specifying Medium
.
$ go test -v
=== RUN TestMakeMediumTank
--- PASS: TestMakeMediumTank (0.00s)
=== RUN TestMakeLargeTank
--- PASS: TestMakeLargeTank (0.00s)
PASS
ok rockets/parts 0.100s
Keep in mind that you are using regular expression matches, so you may accidentally write something that matches every test like this:
$ go test -v -run Tank
=== RUN TestMakeMediumTank
--- PASS: TestMakeMediumTank (0.00s)
=== RUN TestMakeLargeTank
--- PASS: TestMakeLargeTank (0.00s)
PASS
ok rockets/parts 0.248s
REPL Without a REPL Using Test Examples
We Julia developers really love our read-evaluate-print-loop, the REPL. It is the ability to write throwaway code quickly to try out things. How exactly do you do something comparable in Go?
If you are a Go developer, you may be asking why anybody would care to do that. It is not part of your normal routine or habit.
I elaborate on that here: Test-Driven vs REPL-Driven Development.
If you don’t want to wade through that whole article, then here is the key argument I make: Our brains are made for pattern-matching. They are made for reading more than they are made for writing.
You are much better at identifying what is in a picture than drawing it. You are better at reading a sentence in a new language, than uttering it or writing it correctly. The same applies to programming. It is easier to simply push some values into a function and look at what comes out and then decide whether it makes sense than it is to describe ahead of time what ought to come out.
Fortunately, I found a way to mimic my REPL style development in Go using what Go refers to as test examples. This is part of the unit testing system but with a twist. I wrote this example:
func ExampleSpaceVehicle_Launch() {
rutherford := Rutherford{}
boosterEngines := EngineCluster{rutherford, 9} booster := NewStagedRocket(
NewSpaceCraft(nil, NewMediumTank(), rutherford),
NewLargeTank(),
&boosterEngines) ship := NewSpaceVehicle(booster)
fmt.Printf("Before launch, ship mass = %.1f Kg\n", ship.Mass())
fmt.Println(" Booster propellant = ", ship.Propellant(), "Kg") ship.Launch(0.1, 500, func(t float64) {
fmt.Printf("Stage separation at t = %.1f s\n", t)
fmt.Printf(" elevation = %.1f km\n", ship.Elevation/1e3)
fmt.Printf(" propellant = %.1f Kg\n", ship.Propellant())
}) fmt.Printf("After launch, ship mass = %.1f Kg\n", ship.Mass())
fmt.Printf(" elevation = %.1f km\n", ship.Elevation/1e3)
fmt.Println(" Propellant left = ", ship.Propellant(), "Kg") // Output:
// Before launch, ship mass = 12850.0 Kg
// Booster propellant = 9250 Kg
// Stage separation at t = 125.3 s
// elevation = 270.0 km
// propellant = 2050.0 Kg
// After launch, ship mass = 285.0 Kg
// elevation = 2395.2 km
// Propellant left = 0 Kg
}
This doesn’t look like a test at all. There is no comparison of output values. However, Go checks if the output from your program matches what is written after Output:
.
This allows me to fake REPL-style development. I simply write code that I want to test inside a function named ExampleXXX
inside a file called xxx_test.go
. Naturally, the Xs are just placeholders. Then I put in this comment at the end of the file:
// Output:
When I do a go test -v
it will show me the expected output from my print statements. And that is exactly what I wanted. It avoids forcing me to guess what the output should be like in a normal test. These are not really tests in the normal sense. You can think of them more as example code with regression tests. You just copy-paste the output that Go gives you and put into your source code. That may or may not be the correct output.
But this is where our big super duper pattern recognition brains kick into gear. By printing out the right kind of stuff you can get a sense of whether this looks right or not. I don’t do test-driven development (TDD) so this will instead help me devise sensible tests.
What do I mean by sensible tests? By looking at the output and making some reflections on what I’ve made, I can come up ideas on how to properly test correct behavior.
This is how how I do REPL-based development. I test out stuff in the REPL as I go and then I form ideas of how to write a proper test based on that.
Test Examples and Documentation
It is worth noting that these test examples can also be used to document how your functions and methods work. For example, the name of the example ExampleSpaceVehicle_Launch()
is not arbitrary. Rather, it indicates that it documents the Launch
method of the SpaceVehicle
type.
This way, you can document how your methods work with small code examples and add regressions tests on this that will tell you if the example code suddenly starts giving different output than expected.
You actually have stuff like this in Julia as well, but I found it almost useless in practice. I feel sorry about saying this since Julia is my favorite language. The problem is, however, that documentation generation is just too slow. In Go, running tests and creating documentation feels instant.
The Go guys have really done a fabulous job of getting all their stuff to run fast and allow for rapid iteration despite not having a REPL environment at your disposal.
Neat Documentation System
Making and viewing documentation for your custom made packages is super fast and easy. Just write:
$ godoc -http=:6060
Or whatever port number you like. This serves up a webpage with the Go documentation at:
http://localhost:6060/pkg/
Interestingly, this documentation will include both the Go standard library, any library you have downloaded, as well as the custom packages you are building all in one. Here is an example of the documentation that was generated for me. I have collapsed the standard library for easier reading.

Remember my modules file looks like this:
module rockets
go 1.15
replace github.com/ordovician/rockets => ./
require github.com/ordovician/rockets v0.0.0-00010101000000-000000000000
So what you see below the name of my Github account name ordovician is the name of the module rockets
. This module contains the various Go packages I have made. The Go packages are really just subdirectories. In the parts subdirectory every file will contain the header:
package parts
This is so simple and straight forward that it can actually be a bit tricky to realize that this is all there is to it. No registration needs to be done. There are no elaborate XML or YAML configuration files to write for each package. It is all just convention-based.
And I need to repeat one more time. This stuff just runs fast. In Julia, you don’t want to keep rebuilding documentation frequently. In Go, it is instant and they make it so that the Go documentation server observes you code changes. Hence you can change your source code and hit refresh in your web browser and the documentation changes are immediately reflected.