Open this lesson in your favourite AI. It'll walk you through the why, explain the demo, and quiz you on the try-it list.
Go's compiler errors are unusually short and unusually accurate compared to C++ or Rust — but they have a specific style that takes 10 minutes to learn and saves you years. Knowing that 'declared and not used' is a hard error (not a warning), that 'cannot use X (type Y) as type Z' usually means you forgot a pointer or an interface conversion, and that line numbers point to the first place the type system disagreed (not always where you think the bug is) — this is the muscle that turns Go from 'frustrating' to 'fast'.
Go's compiler is famously strict and deliberately terse. declared and not used is the compiler refusing to let dead variables accumulate — a controversial choice that prevents staleness bugs. undefined: X usually means a missing import or a typo in a package path. cannot use X (type T) as type U is Go's static type system doing its job: unlike dynamically-typed languages, the error is caught before you ship. Reading compiler errors well is a force-multiplier: most developers waste minutes searching when the file-and-line location in the error message points exactly at the problem.
var w io.Writer and assign a *bytes.Buffer (works) vs a bytes.Buffer (fails). Read the error.a → b → a. The error tells you the exact file. Fix by extracting shared types.go build ./... (every package) instead of go build .. Sometimes the real bug is in a package you didn't change.Use these three in order. Each builds on the one before.
In one paragraph, explain why Go treats unused variables as errors (not warnings) and the tradeoff that involves.
Walk me through how the Go type checker actually produces errors — single-pass vs two-pass, and why some errors hide behind earlier ones.
When the compiler error points to the wrong line (it sometimes does for generics or large interfaces), what's a systematic way to narrow down the real problem? Cover `gopls`, `go build -gcflags='-m'`, and reduction.
// 1) declared and not used:
package main
func main() {
x := 42 // ./main.go:3:2: x declared and not used
}
// fix: use it, or rename to _
// 2) type mismatch — usually means missing pointer or interface:
package main
import "io"
func main() {
var w io.Writer
w = "hello" // cannot use "hello" (untyped string constant) as type io.Writer
}
// fix: io.Writer needs something with a Write([]byte) method; a string is not.
// 3) circular import:
// package a imports b, b imports a
// ./b.go:3:8: import cycle not allowed
// fix: extract the shared types to package c that both import.go run main.go