The Toolchain Tour: Installing Go with mise, Run, Build, Fmt, Vet
Last lesson ended with a tease: there's a tool that makes installing language toolchains almost unfairly easy. Let's cash that in, get Go onto your machine, and then take the tour that the rest of this course lives inside: run, build, format, vet. By the end you'll have the entire development loop under your fingers, and you'll have noticed something quietly unusual about it: the whole thing is one binary, with zero configuration.
The unfairly easy install
The tool is mise, and I'll state my fandom up front: it's what I use for every language toolchain on every machine I touch. mise is a polyglot version manager. One tool that installs and pins Go, Node, Python, whatever else, either globally or per-project, so "which version am I running?" stops being a question you ever debug.
Two commands. First, install mise itself:
curl https://mise.run | shThen ask it for Go:
mise use -g go@1.27That's the install. The -g means global: this becomes your default Go everywhere. Later, when a project needs a specific version, mise use go@1.27 inside that directory pins it locally and mise switches automatically when you cd in. That trick works for every language mise manages, which is why it's worth adopting now, at the start.
Verify it took:
go version
# go version go1.27.1 darwin/arm64If you see a version string, you're done. The whole ceremony was three commands.
Two notes before moving on. If you'd rather not add a tool, the official installer at go.dev/dl works fine; download, click through, same go version check. And if you're on Windows, use WSL and follow the Linux path; everything in this course assumes a Unix shell.
One binary, many tools
Here's the quietly unusual part. Everything you just installed is a single executable named go, and that executable is the entire toolchain.
Think about what a JavaScript project needs before the first line of real code: Node itself, npm or pnpm for packages, Prettier for formatting, ESLint for linting, Jest or Vitest for tests, probably a bundler, and a config file for each one. None of those decisions are made for you, and every project makes them slightly differently.
Go ships the decisions in the box, as subcommands:
| command | job |
|---|---|
go run | compile and execute, the iterate verb |
go build | produce a real binary |
go fmt | format code into the one canonical style |
go vet | flag code that compiles but looks suspicious |
go test | run tests (module six lives here) |
go doc | read documentation from your terminal |
No config files. Not "sensible defaults you can override", just no config. Lesson one said Go optimizes for the reader; this is what that looks like as tooling. The opinions are part of the language, not an ecosystem you assemble.
A place to stand: go mod init
Every Go project starts the same way: make a directory, declare a module.
mkdir hello && cd hello
go mod init helloA module is Go's unit of project identity: it names your code and, later, tracks what it depends on. The command creates one file, go.mod, and it's two lines:
module hello
go 1.27
Line one is the module's name. In real projects this is a path like github.com/you/feedstack, so other code can import it; for a scratch project, a bare name is fine. Line two records the Go version the module expects. That's the entire file. There's a much deeper story about modules, sub-packages, and project layout, but it waits until lesson 29, because you won't need it for a long while. Two lines is the whole requirement.
Now the file we'll feed to the tools. Create hello.go:
package main
import "fmt"
func main() {
fmt.Println("hello, go")
}Type it, don't study it. Every word of this file, package main, the import, func main, gets dissected next lesson. Right now it's just cargo for the toolchain.
go run, then go build
Two ways to execute this, and the difference matters.
go run .
# hello, gogo run . means "compile the package in the current directory and run it". Spoken aloud: go run dot. The compiled program goes to a temporary location and gets executed; nothing appears in your directory. This is the iterate verb, the one you'll type hundreds of times: change the code, go run ., see the result, repeat. On a program this size the round trip is well under a second, and lesson one explained why that speed was a founding requirement, not luck.
The second verb produces something you can hold:
go build
ls -lh
# go.mod hello.go hellogo build compiled your program into a real executable named hello, sitting right there in the directory. Run it directly:
./hello
# hello, goHere's what's remarkable about that file: it's complete. It contains your code, the Go runtime, everything. Copy it to another machine of the same OS and architecture and it runs. No interpreter to install, no node_modules to ship, no virtual environment to activate. This is the single static binary from lesson one, the property that made Docker and Kubernetes ship as Go programs, and you just produced one with a single command.
go fmt: the argument that isn't
Lesson one told the gofmt story: one canonical style, no options, twenty years of formatting arguments deleted. Now run it. Mangle hello.go first, wreck the indentation, shove a brace somewhere wrong but still legal, then:
go fmt
# hello.goIt prints the names of files it rewrote, and the file snaps back to the one true style. There is nothing to configure and therefore nothing to discuss. Every Go file you will ever read is formatted exactly like the one in front of you, and that sameness is most of why Go gets easy to read so quickly.
go vet: compiles, but suspicious
The compiler catches what cannot compile. go vet catches a different category: code that compiles fine but is probably wrong. Make this edit to the print line:
fmt.Printf("%s is %s\n", "go")Two %s verbs, one argument. This compiles; nothing about it is illegal. Run it and you get garbage output where the second value should be. Now:
go vet
# ./hello.go:6:2: fmt.Printf format %s reads arg #2, but call has 1 argIt names the file, the line, and the exact mismatch. Vet ships with checks like this for a few dozen classic mistakes: format string mismatches, unreachable code, copied locks, misuse of things you haven't met yet. It's a reviewer that never gets tired, and recall the AI-era point from lesson one: every check on that list is a program, which means a coding agent can run vet, read this exact error, and fix its own output before you ever see it.
Your editor runs this loop for you
One last note, brief on purpose: any editor works for this course. If you want the obvious default, VS Code with the official Go extension wires all of this into your editing session via gopls, the Go language server: format-on-save runs gofmt, vet-style findings show up as squiggles while you type, and hover gives you docs. The tour you just did by hand happens automatically on every save. Setup is "install the extension, accept the prompts", and that's all we'll say about it.
The loop you'll live in
That's the toolchain. One binary you installed with one mise command, a module declared in two lines, and four verbs: go run to iterate, go build to ship, go fmt to end formatting discussions, go vet to catch the suspicious. The whole loop runs in seconds, and it's the loop every remaining lesson happens inside.
There's one piece of unfinished business: you've now run a program you haven't actually read. Next lesson we fix that. Every line of hello.go, what the compiler did to it, and what that binary sitting in your directory really is.