Contents

Go(lang) now!

🌱 notes 🌱

I’ve been working in Python and learning Rust, ignoring Go for a few months. Getting set up in a new language (Rust) and circling back to one I’d used before (Python), I bumped into perhaps-avoidable issues getting dev envs set up for each. Restarting a new project with Go, I thought I’d take a few notes to serve as a compact checklist of setting up to work in Go.

Version and package management

Before jumping into things, a note on package management in the context of any general-purpose programming language.

A ‘package’ refers to externally-maintained code that’ll be imported for use by the program. Packages are language-specific, and they’re essential to simplifying things that are - or become - relatively typical in the context of working with that language. Since they’re relatively typical, the language’s standard library may have them covered and, if not, well-maintained (and, frequently, open source) downloads may be available so you can avoid reinventing that particular wheel.

Often, these common capabilities require coordination with the ‘outside’ world: concrete examples include enabling basic program interactivity via standard input/output, or simple networking to provide services to external clients. Interacting with the host operating system and facilitating communication via networking protocols have generalizable but non-trivial components, and not reinventing these for each program delivers numerous benefits beyond the time saved by not reimplementing them from scratch each time.

Such externally maintained packages may evolve, and tracking package versions is important. Being thoughtful about package management up front, before diving in to a project, will make it easier to maintain your code. This also applies to the programming language itself: it will (ideally) be versioned, so managing which version is invoked when the program is compiled or directly interpreted (run) is essential.

For Python, there are a number of options for language version and package management; more on that in a separate post How to train your Python.

For Rust, there’s Cargo - a veritable default, though technically separate from Rust and not strictly required. When just beginning, the pros/cons won’t necessarily be clear for a particular use case, yet a decision as to whether or not to use Cargo is inevitably made (generally in favor of Cargo).

For Go, standardized package management via modules is built-in for Go and has been required since v1.13: it’s necessary to run go mod init before any Go code will compile. This eases the burden of deciding between multiple options up front, helpfully streamlining the process of getting started on any Go project.

Setting the stage

  1. Install Go. Normally, the default GOPATH will be /usr/local/go/bin unless you’re installing via Homebrew on a MacBook Pro with Apple silicon. In that case, GOPATH should be opt/homebrew/opt/go/bin (or opt/homebrew/opt/go@<go-version-number>/bin), so update the environment PATH variable in the shell config file. Be sure to add the new path as a prefix so it’ll be the default.
# in ~/.bashrc
export PATH="/opt/homebrew/opt/go/bin:$PATH"
  1. Create a directory for a project:
# terminal
mkdir go-project
  1. Go code is divided into ‘modules’. A project can be a single module or multiple modules. Starting simply, make the project a single module. Multiple modules can be pulled into a single project later, or they can be imported into a new project that coordinates the independent modules. From within the project directory, initialize go’s module-management of the current module with go mod init <path>; it’ll also manage modules imported by the current module. If the module will be published, <path> is the path from which Go can download the module. If you don’t need to think about this initially, give it a placeholder path.
# terminal
cd go-project
go mod init github.com/github-account/github-project
  1. Create a file in go-project
# terminal
touch gocode-file.go
  1. Open the file in your editor and write some go code. NB: To make the code executable, make package main the first line of the file and ensure that package main includes func main() {...code to execute...} within it.
// gocode-file.go
package main

func main() {
  ...
}
  1. Run the code from the go-project directory: the compiler will build an executable and run it.
# terminal
go run .

Alternately, build the executable to run later.

# terminal
go build .
./go-project

Modules

Go modules are independent building blocks of code, assembled by importing them into new/different modules. You can (and will need to) start using modules from the get-Go(!), but there can be more to using modules than initially meets the eye. Beyond the standard Go docs, I found slides and code from Ryoto Sawada’s 2022 talk Go Module with Microservices and Monorepo: Clear Dependencies with Ease of Development insightful.

The basic of example of importing a Go module:

package main

import "fmt"

func main() {
  fmt.Println("Hello, World!")
}

Your IDE may prompt you to actually import “fmt” but, even if it doesn’t, you’ll need to:

go get fmt

And, then, don’t forget:

go mod tidy

Project structure

Go code is intended to be organized as packages for shared use via API; broadly, such code goes into /pkg directories. Code that’s not intended to be sharable as such goes into /internal directories (at any level of the project structure).

Whatever authoritative take exists on how to structure Go projects lives here (link). And here’s the 2014 ‘Go 1.4 “Internal” Packages’ proposal by Brad Fitzpatrick to provide context around such ‘private’ Go code. Therein, the essence of implementation is succinctly conveyed as: “An import of a path containing the element “internal” is disallowed if the importing code is outside the tree rooted at the parent of the “internal” directory.”

Keep in mind that, to make the code executable:

  • the filename needs to end with .go
  • the file itself will start with package main
  • the file includes a main function as: func main() { <code> }