Learning Go

Notes from learning Go after working primarily with Node.js.

After years of writing Node.js professionally, I spent a few weeks building small tools in Go. This post is a running list of things that surprised me, annoyed me, and eventually won me over.

Goroutines

Goroutines are Go’s unit of concurrency. They are cheap enough that spawning thousands of them is unremarkable, and the runtime multiplexes them onto a small pool of OS threads.

A stylized chart of goroutines being multiplexed onto OS threads

The mental model is close to async/await, except there is no colored function problem: any function can block without holding up a thread.

Channels are typed pipes through which goroutines communicate. Do not communicate by sharing memory; share memory by communicating.

Error handling

Errors are values. There are no exceptions to catch — functions return an error and you deal with it immediately:

func fetch(url string) ([]byte, error) {
 resp, err := http.Get(url)
 if err != nil {
  return nil, fmt.Errorf("fetch %s: %w", url, err)
 }
 defer resp.Body.Close()

 body, err := io.ReadAll(resp.Body)
 if err != nil {
  return nil, fmt.Errorf("read %s: %w", url, err)
 }
 return body, nil
}

Verbose? Sometimes. Explicit? Always.

What I like so far

  1. Single static binaries — go build and copy the file.
  2. The standard library covers most of what I used to reach for packages to get.
  3. go test ./... needs no configuration.

And a short list of things I miss from JavaScript:

  • The npm ecosystem
  • Destructuring everywhere
  • Array.prototype.map

Comparing defaults

Concern Node.js Go
Concurrency Event loop Goroutines
Typing TypeScript (opt-in) Static (built-in)
Deployment node_modules Static binary

Overall: still early, but the language spec is surprisingly readable.