Day 37 — Go 1.26 quality pass

Updated

July 30, 2026

Day 37 — Go 1.26 quality pass

Stage IV · ~3h (theory-heavy + hygiene)
Goal: Apply a deliberate Go 1.26.x quality pass: understand Green Tea GC as default, use new(expr), run the modernized go fix, and leave Stage IV code cleaner and more idiomatic.

Note

This day is about toolchain literacy, not chasing every experimental GOEXPERIMENT. Prefer stable defaults; opt into experiments only with notes.

Why this day exists

Go releases quietly change:

  • Runtime performance (GC)
  • Language ergonomics (new)
  • Tooling (go fix modernizers)
  • Testing/runtime diagnostics

If you never re-read release notes, you write 2019-style code on a 2026 compiler. Today you align your labs with 1.26.


Theory 1 — Confirm your toolchain

go version
# expect go1.26.x

go env GOVERSION GOTOOLCHAIN GOROOT

Module line:

// go.mod
go 1.26

If go mod init on 1.26 created go 1.25.0 (policy: default to N-1), raise deliberately:

go get go@1.26.0
# or edit go.mod go line and go mod tidy

Know the policy: new modules may target the previous minor for broader compatibility—this volume still teaches 1.26 features.


Theory 2 — Green Tea GC is default

From Go 1.26 release notes:

  • Green Tea garbage collector (experimental in 1.25) is enabled by default
  • Aims for better locality/scalability marking small objects
  • Expected ~10–40% reduction in GC overhead in GC-heavy real programs (workload-dependent)
  • Additional gains possible on newer amd64 with vectorized scanning
  • Opt out (temporary): build with GOEXPERIMENT=nogreenteagc
  • Opt-out expected to go away in a future release (notes mention 1.27 direction)

What you should do as an app developer

  1. Don’t micro-tune GC before measuring (Stage VII covers pprof).
  2. Do re-run critical latency benches when upgrading 1.25 → 1.26.
  3. If you suspect GC issues after upgrade, compare with GOEXPERIMENT=nogreenteagc only as a diagnostic, then file issues if truly regressing—don’t permanently cargo-cult the opt-out.
# diagnostic A/B (same benchmark)
go test -bench=BenchmarkFoo -benchmem ./...
GOEXPERIMENT=nogreenteagc go test -bench=BenchmarkFoo -benchmem ./...

Theory 3 — Language: new(expr)

Historically:

p := new(int)
*p = 42

Go 1.26 allows an expression operand:

p := new(42)          // *int pointing to 42
q := new(yearsSince(born)) // allocate + initialize from call result

If expr has type T, new(expr) has type *T and the pointed-to value is initialized to expr.

Why it matters

Optional JSON/protobuf fields often need pointers:

type Person struct {
    Name string `json:"name"`
    Age  *int   `json:"age"`
}

age := 40
p := Person{Name: "Ann", Age: &age}

// 1.26:
p = Person{Name: "Ann", Age: new(40)}
// or new(yearsSince(born))

Cleaner than temporary variables for one-off pointers.

Not a replacement for everything

  • Prefer values when nil-ability isn’t required
  • Don’t invent pointer forests for vanity

Theory 4 — Self-referential type parameters (recap)

Also in 1.26 language notes (Day 32):

type Adder[A Adder[A]] interface {
    Add(A) A
}

Quality pass: if you wrote awkward workarounds, simplify constraints where self-ref is natural.


Theory 5 — Rewritten go fix (modernizers)

In Go 1.26, go fix is revamped:

  • Built on the same analysis framework as go vet
  • Home of modernizers—push-button updates toward modern idioms/APIs
  • Old historical fixers removed
  • Includes suite of language/library modernizations + source-level inlining via //go:fix inline (advanced)
go fix ./...
go vet ./...
go test -race ./...

Discipline

  1. Clean git status before go fix
  2. Run fix
  3. Review diffs like a PR
  4. Run tests
  5. Commit separately: chore: go fix modernizers

Do not blind-apply across huge monorepos without review.


Theory 6 — Testing quality on 1.26

Awareness items from release notes:

  • t.ArtifactDir / -artifacts for retained test outputs
  • B.Loop inlining improvements for benchmarks
  • Prefer modern test helpers you already use (t.TempDir, t.Cleanup)

Optional experiment: goroutine leak profiles in tests for Stage III engines.


Theory 7 — Quality checklist (Stage IV modules)

Check Command / action
Version go version → 1.26.x
Modules tidy go mod tidy
Vet go vet ./...
Fix go fix ./... + review
Test race go test -race ./...
Fuzz smoke go test (seeds)
Formatting gofmt -w . / go fmt ./...
Static hints optional staticcheck (Stage VII deep)

Worked example — new(expr) in tests

func TestPersonJSON(t *testing.T) {
    p := Person{Name: "Ada", Age: new(36)}
    b, err := json.Marshal(p)
    if err != nil {
        t.Fatal(err)
    }
    if !bytes.Contains(b, []byte(`"age":36`)) {
        t.Fatalf("%s", b)
    }
}

Worked example — GC note in README

## Runtime

Built with Go 1.26+. Green Tea GC is the default; no GODEBUG required.
If diagnosing GC regressions vs 1.25, compare GOEXPERIMENT=nogreenteagc builds.

Lab 1 — Inventory & upgrade

Workspace: pick day31day36 modules or a combined Stage IV folder.

go version
# ensure go.mod go 1.26 (or document N-1 policy choice)
go mod tidy

Journal any mismatch between local toolchain and module directive.


Lab 2 — Apply new(expr)

Find ≥2 places using temp vars only to take addresses for optional fields. Rewrite with new(expr) where clearer. Tests remain green.


Lab 3 — go fix pass

git status  # clean
go fix ./...
git diff
go test -race ./...

Write 5 bullet notes on what changed (or “no changes—already modern”).


Lab 4 — GC / runtime awareness lab

  1. Write a small allocation-heavy benchmark (e.g. many small struct allocs).
  2. Run with default GC.
  3. Run with GOEXPERIMENT=nogreenteagc.
  4. Record ns/op and note N=1 machine anecdote ≠ truth—learn the method.
go test -bench=BenchmarkAlloc -benchmem
GOEXPERIMENT=nogreenteagc go test -bench=BenchmarkAlloc -benchmem

Optional: enable leak profile experiment on a known-leaky snippet from Day 25.


Common gotchas

Gotcha Fix
Blind go fix on dirty tree Commit first
Pinning nogreenteagc forever Diagnostic only
Raising go version without CI Align CI Quarto/book env notes separately; app modules use 1.26
Overusing pointers via new Values by default
Skipping tests after fix Always re-test

Checkpoint

  • Running Go 1.26.x locally
  • Explains Green Tea GC default + opt-out diagnostic
  • Used new(expr) idiomatically
  • Ran go fix and reviewed
  • go test -race green after pass
  • Recorded bench A/B method (even if noisy)

Commit

git add .
git commit -m "day37: Go 1.26 quality — new(expr), go fix, GC notes"

Write three personal gotchas before continuing.


Tomorrow

Day 38 — Stage IV gate: generic helper + deep tests (fuzz and/or goldens + concurrent tests) on a real package—prove Stage IV complete.