---
title: 'Production Go: Testing, Tooling and Performance'
source: 'https://academia.sh/en/courses/go-production'
language: en
updated: '2026-09-14T13:45:13+00:00'
license: 'CC BY-SA 4.0'
lessons: 16
outcomes:
    - 'Writes table-driven tests and benchmarks'
    - 'Improves memory allocation behavior by profiling'
    - 'Configures build and distribution options for the target environment'
    - 'Distinguishes whether a piece of information comes from the source, a tool, or a run'
---

# Production Go: Testing, Tooling and Performance

This course is about what is known about a program rather than the program itself: the decision a test delivers, the lines covered, the allocation count, the compiler's escape decision, the files that enter the build, the version embedded in the binary. All of it is measured with one question — who says this, the source, a tool, or a run, and does what it says stay the same every time?

## Testing and Measurement

Setting up test functions and helpers with the language's own package, raising coverage with a case list, server-side and client-side test helpers, faking resting on interface separation, only one of a benchmark's two numbers being reproducible, and what coverage data does and does not say.

### Test Package Basics

Source: https://academia.sh/en/courses/go-production/test-package-basics

The same test file states how many test functions the source contains by counting the text, but states the test's verdict only by running it: against a correct source both tests pass, against a broken source the verdict reverses while the test count stays the same. The limiting measurement runs a test function that carries no assertion and shows that it passes in exactly the same way.

The previous course measured that type information about a value mostly disappears
once it crosses a boundary: someone reading the source usually could not see what type
the value on the far side of a boundary was. This course moves the same question up
one level — now the subject is not a single value but a whole program. You cannot see
from the source whether a program runs correctly, which of its lines ran, where it
allocated memory, or which platform it was built for. So who states this information —
the source itself, an answer a tool derives without running anything, or a result
learned only by actually running it?

This first lesson asks the question in its narrowest form: did a test function pass, or
did it fail? The measurement uses a small package that keeps recurring throughout the
topic — an `inventory` package that holds a store item and the total value of a list of
items. The item itself is simple: a name, a quantity, a unit price in cents. The total
value is a simple product-sum. This lesson's question is not what the package does, but
who states that the package does it **correctly**.

This question carries particular weight in a program running in production. A source
review can read a file's lines and decide the logic looks correct — but there is a gap
between "looked correct" and "was tested to be correct," and the only thing that closes
that gap is a test actually run alongside the source. The topic's remaining five lessons
measure different corners of this same gap: whether the case list itself is written in
the source, how a network layer is tested without ever actually engaging, what a fake
object standing in for a dependency leaves out, which of two writings' numbers is
reproducible, and where the limit of what a coverage ratio states lies. All of them start
from the same root: a test's verdict comes not from the source, but from the run.

#### The Test Function and Helper Follow the Language's Own Rule

The test package is part of the language's own standard library, and it imposes its own
rule: for a function to be recognized as a test by the test package, its name must start
with `Test`, the letter after `Test` must not be lowercase (`TestTotal` is recognized,
`Testtotal` is not), it must take a single `*testing.T` parameter, and the file's name
must end in `_test`. These three rules are not syntax the compiler checks — they are the
test package's own scanning rule, and anyone reading the source who knows this rule can
tell **from the text** which functions will run as tests. The rule itself requires no
registration step: there is no need to add a test function to a separate list or declare
it with a call — the test package finds it on its own the moment the name and signature
are right.

The same file also has a `provideSamples` function, but it is not a test — its name does
not start with `Test`, so the test package never runs it. It is instead a helper called
by two test functions: defined once so the same sample item list is not written
repeatedly, and the `t.Helper()` call in its body tells the test package "report a
failure from the caller's line, not from here." This distinction — which function is a
test and which is a helper — can also be read from the source; the test package does not
have to run any function to make this decision, it only looks at the name and the
signature.

**TM1.** The test file below defines two functions, `TestTotalValue` and
`TestNoAssertion`; both follow the test package's rule with their names and signatures,
and how many there are can be learned by counting the `func Test` occurrences in the
source — with no tool called at all.

```go
// tools/tools.go — shared routines that call the language's own command family in a temporary module from inside a program
package tools

import (
	"os"
	"os/exec"
	"path/filepath"
)

// Setup builds a temporary module with the given files and returns its directory; the path is never printed anywhere.
func Setup(files map[string]string) string {
	dir, _ := os.MkdirTemp("", "trial")
	os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module trial\n\ngo 1.24\n"), 0o644)
	for name, content := range files {
		os.WriteFile(filepath.Join(dir, name), []byte(content), 0o644)
	}
	return dir
}

// Run runs the language's own command in the temporary module; it returns success, not the output itself.
func Run(dir string, extraEnv []string, args ...string) (string, bool) {
	cmd := exec.Command("go", args...)
	cmd.Dir = dir
	cmd.Env = append(append(os.Environ(), "GOTOOLCHAIN=local"), extraEnv...)
	output, err := cmd.CombinedOutput()
	return string(output), err == nil
}
```

`Setup` is needed so the measurement starts from a completely empty module every time;
otherwise a file left behind by a previous run could affect the result. `Run` always
calls the language's own command family the same way: as a subprocess, with a fixed
environment, returning only the output together with a success value. These two routines
are used the same way throughout the topic's six lessons; no lesson names the command in
its prose, it only appears as a call argument inside a code block.

The `tools` functions live in their own package — the package and visibility separation
set up in the Go Fundamentals course works exactly the same way here: `Setup` and `Run`
start with an uppercase letter, so they can be called from the `main` package by
importing the `lesson/tools` path. The measurement's actual body — which source is
being tested, which question is being asked — always stays in the `main` package; the
package boundary is drawn only to write the repeated setup and call logic once and use
it across all six lessons.

#### The Verdict Comes Only From Running

How many tests a test file contains can be read from the source, but whether those tests
**pass** cannot be read the same way. When `go test` is called, the test package first
compiles every file in the package, then finds the functions whose names start with
`Test` and runs each one in turn, checking at the end of each whether it was cut short.
These two steps — compiling and running — are separate: a test file can be compilable
while none of the tests inside it pass, or the reverse. The compile step is also a source
of information, but from a different class: whether a package compiles can be learned by
passing the source through the compiler without ever running the tests — so this is tool
information. Whether a compiled package's tests pass, though, only becomes clear after
compiling finishes, after the tests are actually run; that is run information. The
measurement below keeps the two apart without mixing them: it runs both tests against a
correct version of the `inventory` package, then reruns the same test file, unchanged,
against a broken version of the package.

```go
// measurement.go — the test's verdict is read not from the source but by running it
package main

import (
	"fmt"
	"strings"

	"lesson/tools"
)

const inventoryText = `package trial

type Item struct {
	Name     string
	Quantity int
	Price    int
}

func TotalValue(items []Item) int {
	total := 0
	for _, item := range items {
		total += item.Quantity * item.Price
	}
	return total
}
`

const brokenInventoryText = `package trial

type Item struct {
	Name     string
	Quantity int
	Price    int
}

func TotalValue(items []Item) int {
	total := 0
	for _, item := range items {
		total += item.Quantity
	}
	return total
}
`

const testText = `package trial

import "testing"

func provideSamples(t *testing.T) []Item {
	t.Helper()
	return []Item{{"pen", 3, 250}, {"notebook", 2, 990}}
}

func TestTotalValue(t *testing.T) {
	items := provideSamples(t)
	if got := TotalValue(items); got != 2730 {
		t.Errorf("TotalValue = %d, expected 2730", got)
	}
}

func TestNoAssertion(t *testing.T) {
	provideSamples(t)
}
`

func testCount(source string) int {
	return strings.Count(source, "\nfunc Test")
}

func main() {
	fmt.Println("-- source: test count in the test file --")
	fmt.Println("test function count in the file:", testCount(testText))

	fmt.Println()
	fmt.Println("-- run: verdict against the correct source --")
	correct := tools.Setup(map[string]string{"inventory.go": inventoryText, "inventory_test.go": testText})
	correctOutput, _ := tools.Run(correct, nil, "test", "-v", "./...")
	fmt.Println("TestTotalValue passed:", strings.Contains(correctOutput, "--- PASS: TestTotalValue"))
	fmt.Println("TestNoAssertion passed:", strings.Contains(correctOutput, "--- PASS: TestNoAssertion"))

	fmt.Println()
	fmt.Println("-- run: same test file, verdict against the broken source --")
	fmt.Println("test function count in the file:", testCount(testText))
	broken := tools.Setup(map[string]string{"inventory.go": brokenInventoryText, "inventory_test.go": testText})
	brokenOutput, _ := tools.Run(broken, nil, "test", "-v", "./...")
	fmt.Println("TestTotalValue passed:", strings.Contains(brokenOutput, "--- PASS: TestTotalValue"))
}
```

```
-- source: test count in the test file --
test function count in the file: 2

-- run: verdict against the correct source --
TestTotalValue passed: true
TestNoAssertion passed: true

-- run: same test file, verdict against the broken source --
test function count in the file: 2
TestTotalValue passed: false
```

**TM2.** The only thing that changes between the two runs is the content of
`inventory.go`; the test file is byte-for-byte identical, and the test count counted
from the source is also the same in both: 2. What changes is the verdict these two tests
give **when run** — `TestTotalValue` says `failed` when run against the broken version
that forgets to multiply by the unit price. The only way to foresee this difference by
reading the source is to manually compare the two `TotalValue` implementations and
decide for yourself which one is correct — the test package itself does not reach this
verdict by reading the source, but by actually running the `go test` call. The
measurement above does exactly this: the `Run` routine runs the `go test -v ./...`
command as a subprocess, and the program reads a truth value only by checking for the
presence of a `--- PASS: <name>` line in the output. Neither the error text nor the
test's raw output is printed anywhere — only the single thing asked for is returned:
whether this test passed.

Both runs of the measurement use the same `Setup` routine, the same test file, and the
same command; the only difference is which `inventory.go` content gets written into the
temporary module. This shows that the result comes not from the test framework but from
the source being tested — the measurement itself is written once and invoked twice, and
the difference between the two calls lies only in the input.

The measurement uses the `-v` flag, and this choice is not arbitrary: without the flag,
`go test` gives only a single verdict for the whole package — success if every test in
the package passed, failure if any one failed. Reading what verdict `TestTotalValue` and
`TestNoAssertion` give **separately** requires verbose output, because the test package
only writes this information on a separate line per test when `-v` is requested. The
package-level single verdict is also run information, but at a coarser granularity: it
does not say which test failed, only whether anything failed at all. Once hundreds of
tests accumulate in a single package in a production codebase, the difference between
these two granularities — knowing that something broke versus knowing **what** broke —
turns into a large difference in practice.

#### The Limiting Measurement: What a Passing Test Says

In the run above, `TestNoAssertion` also passed — it would keep passing against the
broken source too (judging by the file), because this function does not test anything at
all. It calls the `provideSamples` helper, discards the returned value without using it,
and contains no `t.Error` or `t.Fatal` call. In the eyes of the test package, a test
function is considered **passed** if it finishes running without being cut short by a
`t.Error`, a `t.Fatal`, or a panic — whether the function's body contains any assertion
at all does not affect this verdict in any way.

**TM3.** This is the topic's first limiting measurement: a passing test does not say it
was **correct**, only that **what it tested held** — and a test that tests nothing at
all passes in exactly the same way, because what the test package measures is not
whether an assertion exists, but whether an existing assertion was cut short.
`TestNoAssertion` is the plainest view of this difference: its name promises a check, but
its body verifies nothing, and the test package itself carries no mechanism to notice
this gap. A test package's not asking this question is not a flaw but its own boundary —
the test package only answers the question "was something cut short while running," not
"is this assertion sufficient." This limit stays true in the topic's later lessons too:
the next lesson counts a case list from the source and asks whether the number of
subtests that run matches the number of cases in the source — but it never asks "are
these cases enough," because the test package never answers that question at all.

This limit has a concrete counterpart in production code: even if a function's body is
left entirely empty with only a comment noting it will be completed later, a test that
calls that function and verifies nothing still passes. A test suite looking green — all
tests appearing to pass — is therefore not on its own sufficient proof; how many
assertions actually ran is a separate question, and the topic's closing lesson re-measures
exactly this distinction through the coverage ratio.

This limit is not a flaw in the test package but a consequence of where its boundaries
were drawn. The test package was designed to answer one question: whether a run was cut
short or not. Whether an assertion is **meaningful** — whether it would give the same
result on a wrong input too, whether it covers a wide enough range — is an entirely
different question, and answering it is the writer's job, not the tool's. `TestNoAssertion`
shows this gap in its plainest form because it contains no assertion at all; but the same
gap can appear just as readily in an assertion written with a condition that, by mistake,
is always true — and in that case the gap is far less visible to an eye reading the
source.

The `inventory` package itself also stays fixed for the rest of this lesson: `Item` and
`TotalValue` keep working on the same example that develops across the topic. The next
lesson tests another function of the same package with a table-driven test, and asks
where the case list itself is written.

#### Summary

- Whether a function counts as a test to the test package depends on its name starting
  with `Test`, taking a single `*testing.T` parameter, and its file name ending in
  `_test`; this rule can be read from the source.
- The test count in the source can be counted from the text, but whether a test passes
  can only be learned by actually running `go test` — the same test file gives
  different verdicts against a correct and a broken version of the package.
- The measurement tool is called from inside the program, and only the requested truth
  value is read; the test's raw output is never printed anywhere.
- The limiting measurement: a passing test does not say it was correct, only that what
  it tested held — a test containing no assertion at all passes in exactly the same way.
- The `inventory` package continues as the same example throughout the topic; this
  lesson used only `TotalValue`.

#### Next Step

This lesson measured that a test's verdict can only be learned by running it, and that a
passing test says "was not cut short," not "correct." The measurement itself also relied
on two routines that will not change for the rest of the topic — `tools.Setup` and
`tools.Run`; the following lessons will call the same two routines again with their own
test file, their own source, and their own question — with one exception, the HTTP
lesson, which never runs `go test` as a subprocess because it calls the handler
directly. The next lesson takes another function of the same package and tests it with a
table-driven test — this time the question is not the test's verdict but the case list
itself: how many cases are written, how many of them actually run a different line, and
whether adding a case always means testing more.

### Table-Driven Tests

Source: https://academia.sh/en/courses/go-production/table-driven-tests

A five-case table's case count is counted from the source, and the number of running subtests is read only by running go test -v — the two match at 5. The limiting measurement shows that adding a sixth case that runs the same lines as an existing one leaves the coverage ratio unchanged at 100.0%.

The previous lesson measured that a test's verdict comes not from the source but from
the run; how many test functions were written could be counted from the source, but
which ones passed could only be learned by running `go test`. This lesson carries the
same distinction one level deeper: into how multiple **cases** are tested inside a
single test function. This time the case list itself is written in the source — one of
the topic's two "source" lines — but how many separate subtests that list produces when
run can still only be read from the run.

This pattern is called a table-driven test, and it is not specific to Go; the same
pattern appears in other languages' test libraries under the name parametrized test —
the taxonomy is not retold here. What this lesson adds is a narrow question: how does
the test package's own `t.Run` routine split a table into subtests, and is the
relationship between the case count and the running subtest count really one-to-one?
Answering this requires setting information that can be counted from the source next to
information read from the run, in the same measurement.

Testing a single function with dozens of inputs is a common need in production
codebases, and meeting that need by writing a separate `func Test...` for every input
produces a file that is both repetitive and hard to read. The table-driven form reduces
this repetition to a body written once; the real question here is whether the case
count standing behind that single body can be read from the source, or only from the
run.

#### The Case List Is Written in the Source

One more function is added to the `inventory` package in this lesson: `Valid` checks in
turn that an item's name is not empty and that its quantity and price are positive, and
returns `false` at the first failed check. The table testing this function is written
as a slice variable where each row corresponds to one case.

`Valid`'s three checks are all written the same way: a condition, an early return. This
shape makes designing the table easier, because writing a separate case for each check
is enough — you do not need to look at the function's body to see which check a case
triggers; the case's own name (`"empty name"`, `"zero quantity"`, `"negative price"`,
`"negative quantity"`) already says which check it targets. The case name itself is also
source information: whoever writes it into the table states, while writing it, which
branch is being tested.

**TM4.** The `cases` slice in the test file below contains five rows; this number can be
read without running anything, just by counting the lines starting with `{"` in the
source — whoever wrote the table also controls, in the source itself, how many cases
were added.

The `tools.Setup` and `tools.Run` routines from the previous lesson are used here exactly
the same way; their definitions have not changed.

```go
// tools/tools.go — shared routines that call the language's own command family in a temporary module from inside a program
package tools

import (
	"os"
	"os/exec"
	"path/filepath"
)

func Setup(files map[string]string) string {
	dir, _ := os.MkdirTemp("", "trial")
	os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module trial\n\ngo 1.24\n"), 0o644)
	for name, content := range files {
		os.WriteFile(filepath.Join(dir, name), []byte(content), 0o644)
	}
	return dir
}

func Run(dir string, extraEnv []string, args ...string) (string, bool) {
	cmd := exec.Command("go", args...)
	cmd.Dir = dir
	cmd.Env = append(append(os.Environ(), "GOTOOLCHAIN=local"), extraEnv...)
	output, err := cmd.CombinedOutput()
	return string(output), err == nil
}
```

```go
// measurement.go — case count is counted from the source, running subtest count is read from the run
package main

import (
	"fmt"
	"strings"

	"lesson/tools"
)

const inventoryText = `package trial

type Item struct {
	Name     string
	Quantity int
	Price    int
}

func Valid(it Item) bool {
	if it.Name == "" {
		return false
	}
	if it.Quantity <= 0 {
		return false
	}
	if it.Price <= 0 {
		return false
	}
	return true
}
`

const testTextFive = `package trial

import "testing"

var cases = []struct {
	name     string
	item     Item
	expected bool
}{
	{"valid", Item{"pen", 3, 250}, true},
	{"empty name", Item{"", 3, 250}, false},
	{"zero quantity", Item{"pen", 0, 250}, false},
	{"negative price", Item{"pen", 3, -10}, false},
	{"negative quantity", Item{"pen", -1, 250}, false},
}

func TestValid(t *testing.T) {
	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			if got := Valid(c.item); got != c.expected {
				t.Errorf("Valid(%v) = %v, expected %v", c.item, got, c.expected)
			}
		})
	}
}
`

func caseCount(source string) int {
	return strings.Count(source, "\n\t{\"")
}

func main() {
	fmt.Println("-- source: case count --")
	fmt.Println("in the five-case table:", caseCount(testTextFive))

	fmt.Println()
	fmt.Println("-- run: running subtest count --")
	dir := tools.Setup(map[string]string{"inventory.go": inventoryText, "inventory_test.go": testTextFive})
	output, _ := tools.Run(dir, nil, "test", "-v", "./...")
	fmt.Println("running subtest count:", strings.Count(output, "--- PASS: TestValid/"))
}
```

```
-- source: case count --
in the five-case table: 5

-- run: running subtest count --
running subtest count: 5
```

**TM5.** The case count read from the source and the running subtest count read from
the run match in this measurement: 5. This match is not a coincidence, it is the
consequence of `t.Run`'s own rule — as the loop walks the `cases` slice, each turn's
`t.Run(c.name, ...)` call opens a subtest, and the test package reports this subtest
with the name it was given (`c.name`): lines such as `TestValid/valid`,
`TestValid/empty_name` appear in the output. The `strings.Count` call counts how many of
these lines end in `PASS`, and as long as the table is written correctly this number
always comes out equal to the length of `cases` — the link between the case count and
the subtest count is built into the loop itself.

This match shows that a single `TestValid` function actually runs five separate checks,
even though it does not appear in the source as five separate functions. In the previous
lesson, what was counted from the source was `func Test` occurrences, giving the test
function count; here what is counted is the data rows inside the same function, giving
the subtest count. Both are information that can be read from the source, but at two
separate levels: how many test functions a file has, and how many subtests a single
test function opens.

Subtest names also go through a small transformation of their own: space characters
inside `c.name` are converted to underscores by the test package, so the `"empty name"`
case appears in the output as `TestValid/empty_name`. This transformation does not
affect the measurement because `strings.Count` only looks for the `--- PASS: TestValid/`
prefix, not the full name — but anyone who reads a case name from the source and wants
to search for the exact same string in the output needs to know this transformation,
because `"empty name"` in the source and `empty_name` at run time point to the same
case without being the same byte sequence.

#### The Limiting Measurement: Adding a Case May Not Raise Coverage

Adding a case to the table intuitively looks like "testing more." The measurement below
shows this is not always true: a sixth case that reruns a line `Valid` already tests is
added to the five-case table — the name is still empty, but the quantity and price
differ — and the coverage ratio is measured for both tables.

```go
// coverage.go — a new case that runs the same line does not change the coverage ratio
package main

import (
	"fmt"
	"strings"

	"lesson/tools"
)

const inventoryText = `package trial

type Item struct {
	Name     string
	Quantity int
	Price    int
}

func Valid(it Item) bool {
	if it.Name == "" {
		return false
	}
	if it.Quantity <= 0 {
		return false
	}
	if it.Price <= 0 {
		return false
	}
	return true
}
`

const testTextFive = `package trial

import "testing"

var cases = []struct {
	name     string
	item     Item
	expected bool
}{
	{"valid", Item{"pen", 3, 250}, true},
	{"empty name", Item{"", 3, 250}, false},
	{"zero quantity", Item{"pen", 0, 250}, false},
	{"negative price", Item{"pen", 3, -10}, false},
	{"negative quantity", Item{"pen", -1, 250}, false},
}

func TestValid(t *testing.T) {
	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			if got := Valid(c.item); got != c.expected {
				t.Errorf("Valid(%v) = %v, expected %v", c.item, got, c.expected)
			}
		})
	}
}
`

const testTextSix = `package trial

import "testing"

var cases = []struct {
	name     string
	item     Item
	expected bool
}{
	{"valid", Item{"pen", 3, 250}, true},
	{"empty name", Item{"", 3, 250}, false},
	{"zero quantity", Item{"pen", 0, 250}, false},
	{"negative price", Item{"pen", 3, -10}, false},
	{"negative quantity", Item{"pen", -1, 250}, false},
	{"empty name, different amount", Item{"", 9, 500}, false},
}

func TestValid(t *testing.T) {
	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			if got := Valid(c.item); got != c.expected {
				t.Errorf("Valid(%v) = %v, expected %v", c.item, got, c.expected)
			}
		})
	}
}
`

func caseCount(source string) int {
	return strings.Count(source, "\n\t{\"")
}

func coverageRatio(output string) string {
	for _, s := range strings.Split(output, "\n") {
		if i := strings.Index(s, "coverage: "); i >= 0 {
			return strings.TrimSpace(s[i+len("coverage: "):])
		}
	}
	return "none"
}

func main() {
	dirFive := tools.Setup(map[string]string{"inventory.go": inventoryText, "inventory_test.go": testTextFive})
	outputFive, _ := tools.Run(dirFive, nil, "test", "-cover", "./...")
	fmt.Println("case count in the five-case table:", caseCount(testTextFive))
	fmt.Println("coverage ratio in the five-case table:", coverageRatio(outputFive))

	dirSix := tools.Setup(map[string]string{"inventory.go": inventoryText, "inventory_test.go": testTextSix})
	outputSix, _ := tools.Run(dirSix, nil, "test", "-cover", "./...")
	fmt.Println("case count in the six-case table:", caseCount(testTextSix))
	fmt.Println("coverage ratio in the six-case table:", coverageRatio(outputSix))
}
```

```
case count in the five-case table: 5
coverage ratio in the five-case table: 100.0% of statements
case count in the six-case table: 6
coverage ratio in the six-case table: 100.0% of statements
```

**TM6.** The case count rises from 5 to 6, but the coverage ratio is the same in both:
`100.0% of statements`. The reason is that the coverage tool measures at the **line**
level: the sixth case reruns `Valid`'s first `if it.Name == "" { return false }` line —
the second case (`"empty name"`) had already run this line once, and in the tool's eyes
it does not matter whether a line runs twice or once, only whether it ran at all. Even
though the new case carries a different `Quantity` and `Price`, `Valid` returns at the
first check without ever looking at these values; the coverage tool cannot see this
difference because what it measures is which **lines** ran, not which **values** were
tried.

In the five-case table, which case runs which line can be traced one by one: `"valid"`
passes all three checks and reaches the final `return true` line, `"empty name"` stops
at the first `return false` line, `"zero quantity"` at the second `return false`,
`"negative price"` reaches and stops at the third `return false`, and
`"negative quantity"` stops again at the second `return false` — the same line as
`"zero quantity"`, despite being a different case. This also clarifies what the sixth
case in the six-case table repeats: the new case `"empty name, different amount"`
shares its first line with `"empty name"`, just as `"negative quantity"` shares its
second line with `"zero quantity"` — in the coverage tool's eyes this overlap makes no
difference at all, it does not count as a second touch.

This is a limit specific to this lesson within the topic's measurement axis: the
coverage tool produces run information (it cannot be known from the source, only
learned by running), but the information it produces is itself coarse — it does not
distinguish whether a line ran once or ten times, or with which inputs it ran. The
five-case table already runs each of `Valid`'s four early returns and its final `true`
return exactly once, so coverage is already at its highest point; the sixth case cannot
climb past this point, because no new line is left to run. The topic's closing lesson
widens this limit and re-measures coverage with a completely different set of cases.

This measurement's production-side counterpart is concrete: during a review, the phrase
"coverage ratio rose to a hundred percent" does not mean that the case most recently
added to the table actually tests a new behavior. A case that reruns the same line with
a different input grows the table without leaving any trace in the coverage report — the
file gets longer, the run time increases, but the number the tool returns stays the
same. This is why coverage ratio is not on its own a sufficient measure for deciding
whether a case is **valuable**; seeing which line has still never run requires looking
at the detailed, line-by-line dump the coverage tool produces — the topic's closing
lesson reads exactly this detail.

This coarseness of the coverage tool is not a flaw but a natural consequence of what it
measures. The tool tracks which lines of the source text have a counterpart in the
compiled binary and how many times each was triggered during a run; tracking **which
value** triggered a line would take the data the tool keeps to an entirely different
level. In a short, lightly branching function like `Valid`, this coarseness makes it easy
to reach the highest ratio with as few as five cases; a more heavily branching function
might need far more cases for the same ratio, but the tool itself asks the same question
every time: did this line run at least once.

#### Summary

- The case count in a table-driven test file can be counted from the source; in this
  lesson the five rows in the `cases` slice were read without calling any tool.
- `t.Run` splits a table into subtests and reports each subtest with the given name; in
  this lesson the running subtest count matched the case count one-to-one.
- Case count comes from the source, but running subtest count can only be learned by
  running `go test -v` — even though the two land on the same number here, they arrive
  by different paths.
- The limiting measurement: adding a case that reruns the same line left the coverage
  ratio unchanged; the coverage tool counts not how many times or with which value a
  line ran, but only whether it ran at all.
- The fake dependency family and coverage measurement were built in the Unit Testing
  and Test-Driven Development course; they are not retold here, only measured through
  Go's own machinery.

#### Next Step

This lesson measured how a table's case count matches the running subtest count, and
that adding a case does not always raise coverage. Both measurements relied on the same
`tools.Setup` and `tools.Run` routines; these routines continue unchanged through the
rest of the topic too — except for the HTTP lesson, which calls the handler directly.
The next lesson carries the same question into the network layer: a server's response to
a request is also information that can only be learned by running it, but this time
without ever opening a real port — how can server-side and client-side test helpers be
set up inside the same process, with the network never engaging at all?

### HTTP Tests

Source: https://academia.sh/en/courses/go-production/http-tests

The same handler is tested with three requests without ever opening a real port: an existing item returns 200, a nonexistent one 404, a request without a parameter 400 — all three run information learned only by calling handler.ServeHTTP. The limiting measurement reads that the request's address is a fixed address RFC 5737 reserves for documentation purposes, showing that the test exercises the handler, not the network.

The previous lesson measured how a table's case count matches its running subtest count,
and whether this match comes from the source or from the run. This lesson carries the
same question into the network layer: a server's response to a request is also
information that can only be learned by running it — but "running" here does not mean
opening a real port, listening on a socket, or having a client call that socket. The
test package's `net/http/httptest` subpackage separates these two: testing a handler's
behavior can mean testing that handler directly with a function call, not over the
network.

If the behavior of an HTTP server running in production can be tested without connecting
to the server directly and sending the request by hand, that test does not **depend** on
the network settings of the machine running the server, on finding a free port, or on a
timeout — meaning its result is independent of the environment. This lesson measures
that claim in two steps: first how a handler is called without a connection at all, then
how the fact that the request this call produces did not come from a real network can
itself be verified.

#### The Handler Is Called Directly, No Port Is Ever Opened

The `inventory` package moves into the HTTP layer in this lesson: `itemHandler` takes a
store (a dictionary from `name` to `Item`) and returns an `http.HandlerFunc`. The
handler decides in three steps: first it reads the `name` parameter from the request's
query string, then checks whether that name exists in the store, and finally, if it
does, writes the item's price as plain text. If the request carries no `name` query
parameter it returns `400`, if the name it carries is not in the store `404`, if it is,
it returns the item's price in the body with `200`. All this handler needs to be able to
connect to an ordinary Go HTTP server is to be registered on an `http.ServeMux` — but
this lesson never connects it to a server at all.

The handler's signature is also a choice that makes this separation easier:
`itemHandler` takes the store as a parameter and returns an `http.HandlerFunc`, the
store itself is not pinned as a package-level variable. This lets the same handler be
rebuilt with a different store — for instance the single-item fixed store used in this
lesson, or an empty store in another measurement; the handler itself never knows which
store it is working with, it only looks at the `map[string]Item` value it was given.

This shape is a closure — the function `itemHandler` returns keeps holding the `store`
variable given at the call that created it, in its own body, just like the previous
lesson's `provideSamples` helper stays bound to its own caller. The test helpers never
go inside this closure; they only call the `http.HandlerFunc` signature visible from the
outside, and which store is held inside the closure stays invisible to the measurement
itself.

The call that builds the request uses the `http.MethodGet` constant instead of writing
the `"GET"` string by hand — these constants defined by the standard library itself
catch a misspelled method name from a single source, if not at compile time, and every
request used throughout this lesson shares the same constant.

Two routines from the `httptest` package make this connection unnecessary:
`httptest.NewRequest` produces the Go equivalent of a request a client would send over
the network — a client-side helper, because normally this request is sent by a client
over a connection. `httptest.NewRecorder` is a structure that implements an
`http.ResponseWriter` and collects a response — a server-side helper, because normally
this is what writes a response to the other end of a connection. Used together, the call
`handler.ServeHTTP(recorder, request)` runs the handler without ever touching a real
network stack, inside the same process, like an ordinary function call.

The `httptest` package also has a `NewServer` routine, and it really does open a port —
it binds the given handler to a temporary, free port on the local machine, returns an
address, and waits for a real client request to be sent to that address. This routine is
needed when you want to test the real network path (opening a connection, sending data,
closing the connection), but this lesson does not use it: testing `itemHandler`'s logic
needs no network path at all, and opening a real port would both slow the measurement
down and add a piece of information — which port happens to be free — that can change
from run to run, exactly the thing this topic forbids writing down. The `Recorder` and
`NewRequest` pair tests the same logic without ever paying this cost.

#### The Verdict of Three Requests

**TM7.** How many distinct status codes are written in the handler's source can be
counted from the source: `http.StatusBadRequest` and `http.StatusNotFound` are written
explicitly, and a third outcome (`http.StatusOK`, `net/http`'s own default when no
status code is written at all) stands implicit, named by no constant — three distinct
outcomes in total can be derived from the source. Which request triggers **which**
outcome, though, is learned not from the source but by actually calling the handler.

`statusCodeCountInSource` does this count without using a tool or a run at all: it takes
the handler's body as a string and checks whether the `StatusBadRequest` and
`StatusNotFound` constants appear in it, counting `StatusOK` itself as an implicit
default since it is never written at all. This is another form of the source-counting
routine set up in the previous two lessons (counting `func Test` occurrences, counting
lines starting with `{"`) — here what is counted is not a function name or a case row,
but a constant's name, though the method is the same: read the source as a string and
count how many times a searched pattern occurs in it.

```go
// measurement.go — the same handler is tested with three requests, no real port is ever opened
package main

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"strings"
)

type Item struct {
	Name     string
	Quantity int
	Price    int
}

func itemHandler(store map[string]Item) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		name := r.URL.Query().Get("name")
		if name == "" {
			http.Error(w, "name required", http.StatusBadRequest)
			return
		}
		it, found := store[name]
		if !found {
			http.Error(w, "not found", http.StatusNotFound)
			return
		}
		fmt.Fprintf(w, "%d", it.Price)
	}
}

const handlerSource = `
		name := r.URL.Query().Get("name")
		if name == "" {
			http.Error(w, "name required", http.StatusBadRequest)
			return
		}
		it, found := store[name]
		if !found {
			http.Error(w, "not found", http.StatusNotFound)
			return
		}
		fmt.Fprintf(w, "%d", it.Price)
`

func statusCodeCountInSource(source string) int {
	n := 1 // the default success return with no written constant
	for _, s := range []string{"StatusBadRequest", "StatusNotFound"} {
		if strings.Contains(source, s) {
			n++
		}
	}
	return n
}

func main() {
	fmt.Println("-- source: how many distinct status codes are written --")
	fmt.Println("status code count written in the handler:", statusCodeCountInSource(handlerSource))

	store := map[string]Item{"pen": {"pen", 3, 250}}
	handler := itemHandler(store)

	fmt.Println()
	fmt.Println("-- run: the verdict of three requests --")
	request1 := httptest.NewRequest(http.MethodGet, "/item?name=pen", nil)
	recorder1 := httptest.NewRecorder()
	handler.ServeHTTP(recorder1, request1)
	fmt.Println("existing item, status code:", recorder1.Code, "body:", recorder1.Body.String())

	request2 := httptest.NewRequest(http.MethodGet, "/item?name=missing", nil)
	recorder2 := httptest.NewRecorder()
	handler.ServeHTTP(recorder2, request2)
	fmt.Println("nonexistent item, status code:", recorder2.Code)

	request3 := httptest.NewRequest(http.MethodGet, "/item", nil)
	recorder3 := httptest.NewRecorder()
	handler.ServeHTTP(recorder3, request3)
	fmt.Println("request without a parameter, status code:", recorder3.Code)
}
```

```
-- source: how many distinct status codes are written --
status code count written in the handler: 3

-- run: the verdict of three requests --
existing item, status code: 200 body: 250
nonexistent item, status code: 404
request without a parameter, status code: 400
```

**TM8.** Even though the source is known to have three distinct status codes written,
which request will trigger which code is only learned once `ServeHTTP` is called — three
`httptest.NewRequest` calls each build a client request; three `httptest.NewRecorder`
values collect these requests' responses separately, and `recorder.Code` returns the
status code the handler actually wrote each time. This is the network-layer counterpart
of the same distinction set up in the previous two lessons: how many outcomes **could
happen** can be counted from the source, which outcome **actually happens** can only be
learned from the run. In Test Package Basics this distinction was whether a function
passed or failed, in Table-Driven Tests it was how many subtests a case turned into;
here the same skeleton is applied to which status code a request turns into — three
lessons run the same frame on a different surface.

It matters that all three requests pass through the same `handler` variable — the
handler is built once, then called three times with three different inputs, just as a
real server's real run has the same handler answering consecutive requests. The
difference is that here these consecutive calls come not from a listening socket but
directly from a loop.

The first request's body is also read separately: `recorder1.Body.String()` returns not
only the status code but also the `250` string the handler wrote with `fmt.Fprintf` —
the `inventory` package's decision from earlier lessons to keep the price in cents
carries through here too. The body is not read for the other two requests, because the
text `http.Error` writes is irrelevant to this measurement's question; the only thing
asked is the status code, and what `http.Error` produces is a version-dependent detail
not written in the source. This is a natural extension of the rule from the second and
third measurements not to print the tool's own output: only the asked measure is read,
everything else is ignored.

#### The Limiting Measurement: What Is Tested Is Not the Network but the Handler

**TM9.** The `RemoteAddr` field of the request `httptest.NewRequest` produces always
carries the same fixed value: `192.0.2.1:1234`. This address is not random — it is a
block RFC 5737 reserves for documentation and example purposes, never routed on any real
network. The `httptest` package uses this same address on every call, because the
request it produces never actually comes from a real connection; since no connection is
ever made, there is no real remote address to fill in either.

On a real HTTP server, the `RemoteAddr` field carries the real address of the client
that opened the connection, and it can change on every request, on every run — exactly
the kind of information this topic has said from the start should never be printed.
`httptest.NewRequest` filling this field with a fixed value is a consequence of the
tool's own design: since a request does not come from a real connection, there is no
real address to fill it with either, and the package fills this gap not with something
random or empty, but with a fixed value that has no counterpart on any real network. This
choice also implies that if a handler's logic looks at the `RemoteAddr` field (an access
restriction, for instance), that logic will always meet the same fixed address under
`httptest` — `itemHandler` carries no such logic in this measurement, but this is a
detail worth remembering when a similar handler is tested in the future: even if an
access restriction works correctly in a real deployment, a test written with this test
helper never actually tests that restriction, because the test itself always runs with a
fixed address.

```go
// boundary.go — the request's address is a fixed block reserved for documentation, not a real connection
package main

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"strings"
)

func main() {
	request := httptest.NewRequest(http.MethodGet, "/item?name=pen", nil)
	isDocumentationAddress := strings.HasPrefix(request.RemoteAddr, "192.0.2.")
	fmt.Println("is the request's address in a block reserved for documentation:", isDocumentationAddress)
}
```

```
is the request's address in a block reserved for documentation: true
```

The test helper used in this lesson, then, tests the handler's **logic** — whether it
produces the correct status code for the input — not the network layer itself. No real
network connection was ever made, listened on, or closed here; the test stayed entirely
inside the same process, in a chain of function calls. This is not a shortcoming but a
deliberate boundary: opening a real socket to test a handler's own logic is an
unnecessary cost and an unnecessary source of flakiness — it adds a failure cause that
has nothing to do with the handler's own logic, such as a port already being used by
another process. But this boundary has a cost too: this test can never catch a defect
that might show up in a real listener's setup, in timeouts, or in concurrent connection
management, because that layer never runs at all in this measurement.

This separation is also visible in the handler's own signature: `http.HandlerFunc` is an
ordinary Go function taking an `http.ResponseWriter` and a `*http.Request` — it takes
neither a socket descriptor nor a network context. All the handler sees is these two
values, and the test helpers are exactly a way of producing these two values without
ever making a real connection. This is why it is not possible to tell, from a handler's
own body, whether it was called from the network or from `httptest` — the handler never
sees the difference between the two, it only works with the `http.ResponseWriter` and
`*http.Request` pair it is given. This is the source of the handler's testability: as
long as the interface itself (these two types) stays fixed, what stands behind it (a
real connection or a recording structure) makes no difference in the handler's eyes. The
next lesson carries this same observation one step further — this time not the handler
itself but a dependency it **calls** is moved behind an interface.

#### Summary

- `httptest.NewRequest` is a client-side helper, `httptest.NewRecorder` a server-side
  one; together they test a handler directly with a function call, without opening a
  real port.
- The status code count in the handler (three) can be counted from the source, but which
  request triggers which code can only be learned by calling `ServeHTTP`.
- The same `handler` variable was called back to back with three different requests; all
  three verdicts were read as run information, the address and port were never printed
  anywhere.
- The limiting measurement: the address `httptest.NewRequest` produces sits in a fixed
  block RFC 5737 reserves for documentation — proof that what is tested is not the
  network but the handler itself.
- This boundary is not a shortcoming but a deliberate choice, and it has a cost too:
  defects tied to a real listener's behavior never show up in this test at all.
- `httptest.NewServer` can open a real port, but this lesson did not use it; testing the
  handler's logic needed no real network path at all, so this cost was excluded from the
  start.

#### Next Step

This lesson measured that a handler's behavior is tested by calling it directly, without
ever opening a real connection: how many distinct outcomes were written in the source
could be counted, which request produced which outcome was only learned by running it,
and that the request itself did not come from a real network was verified by reading its
own address. But the handler itself still used a fixed dictionary built by hand inside
the `main` function — in a real production program this data would come from an
external dependency, a storage layer for instance, and that layer might be too slow or
too unreliable to actually run in every test. The next lesson moves this dependency
behind an interface and measures how the same test body runs with a real and a fake
implementation — showing that what makes the fake possible is the interface being
written in the source.

### Faking and Interface Separation

Source: https://academia.sh/en/courses/go-production/faking-and-interface-separation

The same ReduceStock body is run with both a recording and a spy Notifier implementation: in a normal reduction both give the same verdict, and the spy object's call count reads 1. The limiting measurement shows that in a call reducing more than the stock, the real implementation rejects the negative remainder while the spy object silently records the same call.

The previous lesson measured that an HTTP handler can be tested without ever making a
real connection, but the handler itself still looked at a fixed store built by hand.
This lesson moves that store to another dependency behind the handler — a notification
sender — and asks the same question: if this dependency is something slow, unreliable,
or side-effecting in the real world (writing to an external system, for instance), how
do you test the same logic without touching that dependency at all?

The previous lesson had, in a sense, already solved this question:
`httptest.NewRecorder` was a pre-written fake that implemented the standard library's
own `http.ResponseWriter` interface. This lesson applies the same pattern to a
dependency that has no ready-made fake in the standard library: this package itself
writes `Notifier` and its two implementations.

The fake dependency family — mock object, stub, imitation object, spy object, dummy
object — was built with its taxonomy in the Unit Testing and Test-Driven Development
course and is not reintroduced here; neither is the taxonomy retold nor is every member
implemented separately in this lesson. This lesson's added question is narrow: what
makes faking possible, and where does that thing appear in the source?

In production codebases it is often not possible to test everything a function calls in
its real form: a notification sender might write to a real external system, a payment
provider might move real money, a storage layer might connect to a real server. A fake
standing in for these dependencies frees the test from these costs — but this freedom
has its own price, and this lesson's limiting measurement counts exactly that price. As
long as the price stays invisible it carries no risk; this lesson's goal is to make the
price visible, not to make you abandon faking.

#### What Makes Faking Possible Is Interface Separation

One more function is added to the `inventory` package: `ReduceStock` reduces an item's
quantity and sends a notification when the remaining amount drops below a certain
threshold — the threshold is fixed at 2 in this lesson. `ReduceStock` does not call the
notification sender directly — it calls it through a `Notifier` **interface**.

```go
// inventory.go — ReduceStock is bound not to a concrete notification sender but to an interface
package main

import (
	"errors"
	"fmt"
)

type Item struct {
	Name     string
	Quantity int
	Price    int
}

type Notifier interface {
	Notify(name string, remainingQuantity int) error
}

func ReduceStock(store map[string]Item, name string, amount int, n Notifier) error {
	it := store[name]
	it.Quantity -= amount
	store[name] = it
	if it.Quantity < 2 {
		return n.Notify(name, it.Quantity)
	}
	return nil
}

// recordingNotifier is the real implementation: it rejects a negative remainder.
type recordingNotifier struct {
	records []string
}

func (r *recordingNotifier) Notify(name string, remainingQuantity int) error {
	if remainingQuantity < 0 {
		return errors.New("a negative remainder cannot be reported")
	}
	r.records = append(r.records, fmt.Sprintf("%s:%d", name, remainingQuantity))
	return nil
}

// spyNotifier is the fake implementation: it records the call without validating it.
type spyNotifier struct {
	calls int
}

func (s *spyNotifier) Notify(name string, remainingQuantity int) error {
	s.calls++
	return nil
}
```

**TM10.** `ReduceStock`'s last parameter is `Notifier` — an interface consisting of
nothing but a method signature, not a concrete type like `recordingNotifier` or
`spyNotifier`. This is a decision that can be read directly from the source: the code
calling `ReduceStock` can pass in **any** type that carries the `Notify` method, and
`ReduceStock`'s body never knows which concrete type was passed. This is exactly what
makes faking possible: if `ReduceStock` took `*recordingNotifier` directly, there would
be no way at all to pass in something else in its place.

The `inventory` package gained exactly one new dependency throughout this lesson, and
this dependency was defined inside the package itself — `Notifier` is not a type
imported from a real external package, it is `inventory`'s own interface, defining only
the single method `ReduceStock` needs. This is an example of the consumer of an
interface (here, `ReduceStock`) defining a small interface to fit its own needs: an
interface is not the broad contract a library exposes to the outside world, but the
narrow contract that covers only as much as the caller actually uses.

The `Notifier` interface carries a single method, and this narrowness is deliberate: the
narrower an interface is, the less work it takes to write a type that implements it.
Both `recordingNotifier` and `spyNotifier` satisfy the interface by defining only this
one method (`Notify`) — in Go, a type does not have to separately declare that it
satisfies an interface; if its method set covers the set the interface asks for, the
type satisfies that interface on its own. This is why `spyNotifier` can be passed to
`ReduceStock` while having no connection at all to the rest of the `inventory` package,
only by carrying the `Notify` method.

The interface itself guarantees that the real and the fake implementation carry the
**same contract**: both can be called with the same signature
(`Notify(name string, remainingQuantity int) error`), and `ReduceStock`'s body runs the
same line (`n.Notify(name, it.Quantity)`) without distinguishing which one it is working
with. But this contract only guarantees the **signature** — not the **behavior** behind
it. Whether the two implementations give the same answer to the same input cannot be
read from the interface itself at all; the answer to that question emerges in the
measurement below.

#### Same Body, Two Implementations

`ReduceStock`'s body has exactly one line concerning the `Notifier` type:
`n.Notify(name, it.Quantity)`. Which concrete type this line works with depends on which
value the caller passed to `ReduceStock` — the function itself never makes this
decision. The measurement below leaves this decision to the caller, calling the same
function twice with two different third arguments.

**TM11.** The same `ReduceStock` call is run first with the real implementation
(`recordingNotifier`), then with the fake implementation (`spyNotifier`); two are
reduced from a three-quantity item, the remainder (1) is below the threshold, so the
notification fires.

```go
// measurement.go — the same body runs with a real and a fake implementation
package main

import "fmt"

func main() {
	fmt.Println("-- run: same body with the real and the fake --")
	store1 := map[string]Item{"pen": {"pen", 3, 250}}
	real := &recordingNotifier{}
	errReal := ReduceStock(store1, "pen", 2, real)
	fmt.Println("real: error present:", errReal != nil, "record count:", len(real.records))

	store2 := map[string]Item{"pen": {"pen", 3, 250}}
	fake := &spyNotifier{}
	errFake := ReduceStock(store2, "pen", 2, fake)
	fmt.Println("fake: error present:", errFake != nil, "spy call count:", fake.calls)
}
```

```
-- run: same body with the real and the fake --
real: error present: false record count: 1
fake: error present: false spy call count: 1
```

The measurement builds two separate `store` variables (`store1`, `store2`), with the
same content but as separate maps — since `ReduceStock` mutates the store itself, if a
single store were used twice, the second call would start from the state the first call
left changed, and the two implementations' verdicts would no longer rest on the same
input. This isolation rests on the same reasoning as `tools.Setup`'s building a
completely empty module on every run in the earlier lessons: one measurement must not
carry the trace of a previous one.

The body of `ReduceStock` is byte-for-byte identical across both runs; the only thing
that changes is the value passed as the last argument. In the real implementation, the
record list's length rises to 1 — this tracks a real side effect. In the fake
implementation, the same call raises the `calls` counter to 1 — all the spy object
measures is this: **how many times** it was called, not **what** it did. Both
measurements arrive at the same result (`error present: false`), and to this extent it
can be said that the fake stands in for the real one.

`spyNotifier` is specifically a spy object here — the member of the fake dependency
family that records calls and allows them to be queried afterward. `Notify` does
nothing when called, it only increments a counter; this is enough to test whether
`ReduceStock` fired a notification, because the measurement's question is "was a
notification sent," not "was the notification's content correct." The same scenario
could be extended with a spy that also keeps the call's arguments instead of just the
count — `spyNotifier` is kept in its narrowest form here, just a counter, because that
is all this measurement asks.

The name `recordingNotifier` is itself a choice too: this type is not just a dummy that
records calls, it is an implementation that really makes a decision — rejecting a
negative remaining quantity is not a fake rule, it is logic that would go into
production. This lesson calls it "real" because it builds a small model of the actual
implementation `ReduceStock` will work with in production — it, too, writes to no
external system in this lesson, but the validation rule it carries is of the kind a real
external system might carry.

#### The Limiting Measurement: The Fake Implementation Tests Less

`ReduceStock` itself does not reject a call that would push the quantity negative — this
is a design decision: how reducing more than the stock is handled was left to
`Notifier`'s responsibility, not `ReduceStock`'s. This is a branch invisible in the
previous measurement; when two were reduced from a three-quantity item, the remainder
always stayed positive.

**TM12.** The same measurement is now run reducing five from a three-quantity item —
the remaining quantity drops negative (-2) and `Notify` is called with this negative
value.

```go
// boundary.go — an assumption holding in the real implementation is never tested in the fake one
package main

import "fmt"

func main() {
	fmt.Println("-- limiting measurement: reducing more than the stock --")
	store3 := map[string]Item{"pen": {"pen", 3, 250}}
	real2 := &recordingNotifier{}
	errReal2 := ReduceStock(store3, "pen", 5, real2)
	fmt.Println("real: error present:", errReal2 != nil)

	store4 := map[string]Item{"pen": {"pen", 3, 250}}
	fake2 := &spyNotifier{}
	errFake2 := ReduceStock(store4, "pen", 5, fake2)
	fmt.Println("fake: error present:", errFake2 != nil, "spy call count:", fake2.calls)
}
```

```
-- limiting measurement: reducing more than the stock --
real: error present: true
fake: error present: false spy call count: 1
```

The call count comes out the same in both implementations — `fake2.calls` is still 1,
because `Notify` is still called exactly once. What changes is the **outcome** of the
call, not the call itself; the spy object correctly counts whether the call happened,
but never looks at whether the value the call carried is valid. This time the two
implementations reach **different** verdicts. `recordingNotifier` encodes an assumption
a real external system might carry in the real world: a negative remaining quantity
cannot be reported, because such a state is meaningless in the external system's eyes.
When this assumption is violated, the real implementation returns an error, and
`ReduceStock` itself carries this error along unchanged to its caller. `spyNotifier`
carries no assumption at all — it only records that it was called, and always reports
success; called with the same negative value, it silently raises `calls` by 1 and
returns `nil`.

This is a limit specific to this lesson within the topic's measurement axis: a test
suite using only `spyNotifier` never tests whether `ReduceStock` correctly handles the
case of reducing more than the stock — because the spy object itself does not
distinguish this case, it accepts every call in the same way. An assumption that holds
in the real implementation goes entirely untested in the fake implementation; a faking
test therefore tests **less** than the real test, not more. This is not a flaw but the
nature of a fake: a fake does not carry all the rules of the real system behind it, only
as much as the person who wrote it encoded.

This has a concrete production-side counterpart: even if a test suite written only with
a spy object looks green, `ReduceStock` will hit an error in this scenario once the real
external system engages — and the rule producing that error might be written in the
external system's own documentation, yet never carried into the fake implementation at
all. This is another form of the limit measured in Test Package Basics: there, a passing
test did not say it was correct, only that what it tested held; here, a passing faking
test says only that the fake's **own** rules held — not the real system's rules. Both
limits trace to the same root: a test passing carries no claim about anything outside
the scope of what it tested. The number of methods the fake implements is the same as
the real implementation here (both implement only `Notify`), but the number of
**behaviors** is not: the real implementation carries two behaviors (accepting,
rejecting), the fake carries only one (accepting). The interface itself never shows this
difference, because an interface only enforces the signature, not the number of
behaviors.

This limit does have an exit, though it is outside this lesson's scope: writing a
separate contract test that periodically tests the fake implementation against the real
implementation's assumptions could separately verify that the two sides' behavior does
not drift apart. There is no such contract test in this lesson — the goal is to show the
fake's limit, not to offer a solution that closes it.

`spyNotifier` is the right choice for this measurement, because the question asked is
"how many times was it called" — the other members of the fake dependency family exist
to answer different questions. A stub would return a fixed value and record nothing; a
dummy object would only fill a parameter slot, and it would make no difference if
`Notify` were never called at all. The behavior of `ReduceStock` measured here — whether
the notification is **triggered** and **how many times** it is triggered — is exactly
the kind of information a spy object measures; that is why this lesson used only that
one of the five members.

#### Summary

- `ReduceStock`'s last parameter is not a concrete type but an interface (`Notifier`);
  this separation can be read directly from the source, and it is exactly what makes
  faking possible — the information that makes faking possible is stated by the
  **source**.
- The same `ReduceStock` body was run with a real (`recordingNotifier`) and a fake
  (`spyNotifier`) implementation; in a normal reduction both gave the same verdict.
- What the spy object measures is only the call count — not what it did, but how many
  times it was called.
- The limiting measurement: in a call reducing more than the stock, the real
  implementation rejected the negative remainder, the fake implementation silently
  accepted the same call — the faking test never tested this assumption at all.
- The fake dependency taxonomy was built in the Unit Testing and Test-Driven Development
  course; it is not retold here, only measured through Go's own interface machinery.

#### Next Step

This lesson measured that an interface makes a dependency swappable between a real and a
fake implementation, but that the fake implementation does not carry all the real one's
assumptions. `Notifier` stayed a narrow, single-method interface; `recordingNotifier`
carried its own validation rule, `spyNotifier` kept only a call counter — and the two
implementations gave the same verdict in a normal reduction and different verdicts in an
excessive one. The four lessons so far each passed or failed one at a time, gave or did
not give an error one at a time — but none of them asked which **number** among these
verdicts changes from run to run. The next lesson asks exactly this question: when two
writings of the same work are compared, which number stays the same across five separate
runs, and which does not — and the topic's binding numbers are also set up there.

### Benchmarking

Source: https://academia.sh/en/courses/go-production/comparative-measurement

Eleven pieces of information are gathered about one program and split among three tellers — 2 by the source, 5 by a tool with no run needed, 4 only by running it. The benchmark tool produces one of these four run pieces of information: the allocation count of two writings of the same work gives the same value across five runs; the limiting measurement shows that even this number cannot be compared without saying what it counts.

The previous four lessons each asked one question at a time about a program: did a test
pass, how many subtests did a case turn into, which status code did a request trigger,
did a dependency's real and fake versions give the same verdict. Each question carried
its own source/tool/run class, but none of them asked how these classes are distributed
**in total**. This lesson steps back and asks the same question all at once: if we
gather eleven separate things that can be known about a program, how many are written in
the source, how many does a tool state without a run, how many only emerge by running
it?

This lesson is also where the measurement the course's name points to — the benchmark —
first appears. This measure produces two numbers on its own: how much memory a piece of
work allocates and how long it takes. Only the first of these is used in this course; why
the second is left out is the subject of the second half of this lesson.

#### Eleven Pieces of Information, Three Tellers

The measurement below writes a small source — a struct, two functions, a three-case test
table, an extra file kept by a build tag — into a temporary module, then asks eleven
separate questions about this one source. Each question is passed to the `observe`
routine together with who states the answer (source, tool, run); `observe` counts these
three classes. This gathers everything the previous four lessons asked separately under
one roof; each question is answered with a mechanism already seen before (reading the
source as a string and counting, calling a tool from inside the program and reading only
its result).

```go
// tools.go — the shared routines of this "who states this" frame, and two benchmark writings
package main

import (
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"testing"
)

var source, tool, run int

func observe(fact, statedBy, evidence string) {
	switch statedBy {
	case "source":
		source++
	case "tool":
		tool++
	default:
		run++
	}
	fmt.Printf("%-35s %-7s %s\n", fact, statedBy, evidence)
}

func buildModule(files map[string]string) string {
	dir, _ := os.MkdirTemp("", "measurement")
	os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module measurement\n\ngo 1.24\n"), 0o644)
	for name, content := range files {
		os.WriteFile(filepath.Join(dir, name), []byte(content), 0o644)
	}
	return dir
}

func runTool(dir string, env []string, args ...string) (string, bool) {
	cmd := exec.Command("go", args...)
	cmd.Dir = dir
	cmd.Env = append(append(os.Environ(), "GOTOOLCHAIN=local"), env...)
	output, err := cmd.CombinedOutput()
	return string(output), err == nil
}

func escapeCount(output string) int { return strings.Count(output, "escapes to heap") }

func coverageRatio(output string) string {
	for _, s := range strings.Split(output, "\n") {
		if i := strings.Index(s, "coverage: "); i >= 0 {
			return strings.TrimSpace(s[i+len("coverage: "):])
		}
	}
	return "none"
}

// growSlice appends eight times without preallocating.
func growSlice(b *testing.B) {
	for i := 0; i < b.N; i++ {
		var s []int
		for j := 0; j < 8; j++ {
			s = append(s, j)
		}
		_ = s
	}
}

// preallocSlice does the same work, appending into a space preallocated for eight elements.
func preallocSlice(b *testing.B) {
	for i := 0; i < b.N; i++ {
		s := make([]int, 0, 8)
		for j := 0; j < 8; j++ {
			s = append(s, j)
		}
		_ = s
	}
}

// runFiveTimes runs the same benchmark five times; it returns the first value and how
// many distinct values were seen.
func runFiveTimes(k func(*testing.B)) (first int64, distinctValues int) {
	seen := map[int64]bool{}
	for i := 0; i < 5; i++ {
		r := testing.Benchmark(k)
		seen[r.AllocsPerOp()] = true
		if i == 0 {
			first = r.AllocsPerOp()
		}
	}
	return first, len(seen)
}
```

**TM13.** Two of the eleven questions asked are answered from the source: the case count
in the test table and the module's language version — both are written in the text
itself, readable without running anything.

```go
// measurement.go — eleven pieces of information, split among three tellers
package main

import (
	"fmt"
	"os"
	"strings"
)

const sourceText = `package main

import "fmt"

type Record struct{ Name string }

func staysLocal() int {
	r := Record{Name: "bolt"}
	return len(r.Name)
}

func escapes() *Record {
	r := Record{Name: "nut"}
	return &r
}

var version = "unknown"

func main() {
	fmt.Println(staysLocal(), escapes().Name, version)
}
`

const testText = `package main

import "testing"

var cases = []struct {
	name     string
	input    string
	length   int
}{
	{"short", "ab", 2},
	{"medium", "abcd", 4},
	{"empty", "", 0},
}

func TestLength(t *testing.T) {
	for _, c := range cases {
		if len(c.input) != c.length {
			t.Errorf("%s: expected %d", c.name, c.length)
		}
	}
	if staysLocal() != 4 {
		t.Error("staysLocal")
	}
	if escapes().Name != "nut" {
		t.Error("escapes")
	}
}
`

const taggedText = `//go:build extra

package main

func extra() int { return 1 }
`

func main() {
	fmt.Println("-- who states this --")

	dir := buildModule(map[string]string{"main.go": sourceText, "main_test.go": testText, "extra.go": taggedText})
	defer os.RemoveAll(dir)

	observe("table case count", "source", fmt.Sprint(strings.Count(testText, "\t{\"")))
	observe("module language version", "source", "written in the go.mod line")

	escapeOutput, _ := runTool(dir, nil, "build", "-gcflags=-m", "-o", os.DevNull, ".")
	observe("heap-escaping value count", "tool", fmt.Sprint(escapeCount(escapeOutput)))

	_, vetClean := runTool(dir, nil, "vet", "./...")
	observe("static analysis finding", "tool", fmt.Sprint("clean=", vetClean))

	_, crossBuilt := runTool(dir, []string{"GOOS=linux", "GOARCH=arm64"}, "build", "-o", os.DevNull, ".")
	observe("buildability for another platform", "tool", fmt.Sprint("built=", crossBuilt))

	untagged, _ := runTool(dir, nil, "list", "-f", "{{len .GoFiles}}", ".")
	tagged, _ := runTool(dir, nil, "list", "-tags", "extra", "-f", "{{len .GoFiles}}", ".")
	observe("file count changed by build tag", "tool",
		fmt.Sprint("untagged=", strings.TrimSpace(untagged), " tagged=", strings.TrimSpace(tagged)))

	_, testPassed := runTool(dir, nil, "test", "./...")
	observe("test verdict", "run", fmt.Sprint("passed=", testPassed))

	coverageOutput, _ := runTool(dir, nil, "test", "-cover", "./...")
	observe("line coverage ratio", "run", coverageRatio(coverageOutput))

	linkedOutput, _ := runTool(dir, nil, "run", "-ldflags=-X main.version=1", ".")
	fields := strings.Fields(strings.TrimSpace(linkedOutput))
	observe("version embedded in the binary", "tool", fields[len(fields)-1])

	firstAllocs, distinctValues := runFiveTimes(growSlice)
	observe("allocation count", "run", fmt.Sprint(firstAllocs, " distinct values across five runs=", distinctValues))

	filterOutput, _ := runTool(dir, nil, "test", "-run", "TestLength", "-v", "./...")
	observe("filter-selected test count", "run", fmt.Sprint(strings.Count(filterOutput, "--- PASS")))

	fmt.Printf("information: %d source, %d tool, %d run\n", source, tool, run)
}
```

```
-- who states this --
table case count                    source  3
module language version             source  written in the go.mod line
heap-escaping value count           tool    3
static analysis finding             tool    clean=true
buildability for another platform   tool    built=true
file count changed by build tag     tool    untagged=1 tagged=2
test verdict                        run     passed=true
line coverage ratio                 run     80.0% of statements
version embedded in the binary      tool    1
allocation count                    run     4 distinct values across five runs=1
filter-selected test count          run     1
information: 2 source, 5 tool, 4 run
```

**TM14.** Five pieces of information come from a tool: heap-escaping value count, the
static analyzer's finding, buildability for another platform, the file count changed by
the tag, the version embedded in the binary. What these five share is that they
**require no run** — the compiler, the checker, the analyzer, and the linker each give
the same answer to the same source every time they are called.

**TM15.** The remaining four pieces of information can only be learned by running:
the test verdict, the covered line ratio, the allocation count, the filter-selected test
count. The sum of the eleven pieces of information accumulates in a single run for the
first time in this lesson: **2 source, 5 tool, 4 run**. This is the topic's binding
distribution — this number is referred to with a single sentence in every lesson after
this one, it is never reproduced again.

Each of these eleven lines is asked in exactly the same way the previous four lessons'
questions were asked — only this time all of them are gathered in a single run, against a
single source. The `table case count` line repeats Table-Driven Tests' question, the
`test verdict` line repeats Test Package Basics' question exactly; the only difference is
that here all these questions are gathered in the same run and turned into a
distribution. **Nine** of the eleven pieces of information (the tool and run sum)
require a run or at least one tool call; reading the source alone gives only two of
these eleven — the great majority of what can be known about the program itself lies
outside the text.

#### Only One of the Benchmark's Two Numbers Is Reproducible

The tenth of the eleven lines above — the allocation count — is this lesson's real
subject, and it is measured differently from the remaining ten:
`testing.Benchmark` runs the same work five times, `AllocsPerOp` returns one number from
each run, and how many **distinct** values these five numbers fall into is counted
separately. In the run above, this number is 1 — five runs, a single value.

`growSlice` was already used once in the full run above — it was the evidence for the
"allocation count" line. The measurement below calls it again, but this time not alone,
together with a second writing it will compare against.

**TM16.** The measurement below compares two writings of the same work: `growSlice`
appends eight elements without preallocating, `preallocSlice` appends the same eight
elements into a space preallocated beforehand. Both are run five times each.

```go
// comparison.go — the same work's two writings compared by allocation count
package main

import "fmt"

func main() {
	fmt.Println("-- benchmark: two writings compared by allocation count --")

	noPreallocFirst, noPreallocDistinct := runFiveTimes(growSlice)
	fmt.Println("without preallocation, allocations per operation:", noPreallocFirst,
		"distinct values across five runs:", noPreallocDistinct)

	preallocFirst, preallocDistinct := runFiveTimes(preallocSlice)
	fmt.Println("with preallocation, allocations per operation:", preallocFirst,
		"distinct values across five runs:", preallocDistinct)
}
```

```
-- benchmark: two writings compared by allocation count --
without preallocation, allocations per operation: 4 distinct values across five runs: 1
with preallocation, allocations per operation: 0 distinct values across five runs: 1
```

The benchmark tool itself, here just as in the previous run, returns two separate
numbers — allocations per operation and elapsed time. This lesson **never prints,
measures, or compares the time anywhere**: only `AllocsPerOp` is read from what
`testing.Benchmark` returns; the field carrying elapsed time is never used at all. The
reason for this is not a preference but an observation: even measured five times in a row
on the same machine, on the same source, elapsed time changes from run to run — it
depends on what else the operating system happens to be running at that moment, on the
cache's state at that moment, even on how much the measurement itself warms the machine.
Allocation count is not like this: `growSlice` allocates exactly four times on every run,
because the slice's underlying array goes through the same growth decisions every time
until it reaches eight elements, and this chain of decisions does not change from run to
run.

`preallocSlice` allocates zero times — the space for eight elements is allocated all at
once with `make([]int, 0, 8)`, and the compiler's escape analysis (this separation
itself is measured in the topic's next topic) can keep this slice on the stack without
ever moving it to the heap. The gap between four and zero is the concrete counterpart of
preallocating: the same eight elements are added in the same order, but one is left to
the slice's own growth mechanism (doubling capacity as it fills), while the other asks
for enough space up front.

#### The Limiting Measurement: Even Allocation Count Cannot Be Compared Without Saying What It Counts

**TM17.** The two writings' allocation counts — 4 and 0 — do not on their own say "one
is better"; without saying **what** each number counts, these two values cannot be
compared. What `AllocsPerOp` returns is allocations **per operation**:
`testing.Benchmark` decides `b.N` on its own, runs the inner loop that many times, and
produces this number by dividing the total allocations by `b.N`. The total allocation
count — how many times memory was allocated across the whole run — is a separate number,
and it grows as `b.N` grows; the per-operation number stays fixed while the total number
never stays fixed at all.

When this distinction is missed, a misleading comparison follows: the sentence "the
first writing allocates four times more memory than the second" is correct, but "the
first writing allocated four times more memory in total" is a different claim — the
second sentence assumes the two runs' `b.N` values were the same, and that assumption
was never checked anywhere. The number this lesson measures is always allocations per
operation; total allocations were never computed in this lesson at all, because the
topic's question is "which writing allocates less memory per operation" — not "how much
memory did this run allocate in total." If a benchmark result is shared without saying
which of these two numbers is meant, the same two numbers (4 and 0) can be offered as
evidence for an entirely different claim.

The `runFiveTimes` routine itself carries this distinction implicitly too: the `first`
value it returns is each run's `AllocsPerOp` — already a per-operation number, already
divided by `b.N` by `testing.Benchmark`. This routine never sees the total allocation
count at all, because `testing.Benchmark` itself never exposes that number — it returns
only allocations per operation and time per operation. This is a consequence of the
measurement tool's own design — benchmark tools generally prefer the per-operation
number, because it is exactly this normalized form that makes two measurements run with
different `b.N` values comparable at all.

#### Summary

- Eleven pieces of information were gathered in one run and split into three classes:
  **2 source, 5 tool, 4 run** — this distribution is the topic's binding number, it is
  not reproduced in later lessons.
- Five pieces of tool information require no run, giving the same answer to the same
  source on every call; four pieces of run information can only be learned by actually
  running it.
- The benchmark tool returns two numbers, but this lesson read only one of them —
  allocations per operation; time was never printed anywhere, never compared anywhere.
- Allocation count landed on the same value across five runs (1 distinct value); this is
  proof that the number is reproducible.
- The limiting measurement: allocations per operation and total allocations are separate
  numbers; a comparison cannot be read without saying which one is meant.

#### Next Step

This lesson measured how eleven pieces of information about a program split into three
classes, and that only one of the benchmark's two numbers is reproducible. Five pieces of
tool information and four pieces of run information stayed within this lesson's own
example; both reappear in their own corner in later lessons, but the full eleven-line
table is never printed again. One of these eleven pieces of information — covered line
ratio — passed in this lesson only as a single number: `80.0% of statements`. The next
lesson measures exactly what this number counts, which lines are left out of coverage,
and how three separate test sets change this ratio — the topic's closing lesson.

### Coverage Reporting

Source: https://academia.sh/en/courses/go-production/coverage-reporting

The same function tested with three separate test sets gives three separate coverage ratios — 57.1%, 85.7%, 100.0% — and the uncovered block count drops correspondingly to 3, 1, and 0. The limiting measurement shows, by running the third set — the one that gives 100% coverage — against a broken source, that it catches no real defect at all in a branch whose return value it never checks.

The previous lesson split eleven pieces of information about a program into source,
tool, and run classes; one of these eleven — covered line ratio — passed in that lesson
only as a single number (`80.0% of statements`), with no detail given. This closing
lesson is the topic's wrap-up, and it opens that detail: what exactly does coverage
ratio count, and what actually changes when this number rises?

Coverage ratio is a commonly used indicator in production codebases: a review, a
threshold ("new code must be covered at least this much"), or a single percentage shown
on a dashboard. What this indicator says and does not say is the final exam of the "who
states this" question the topic's five lessons have built — because coverage, looked at
on its own, proves not that a program was tested, but only that it was run. To answer
this question, the lesson returns one last time to the `inventory` package used
throughout the topic; this time the subject is not the package itself but the three
different test sets that test the same package.

#### Same Function, Three Test Sets, Three Ratios

One last function is added to the `inventory` package: `Label` looks at an item's
quantity and returns one of four states — a negative quantity `"invalid"`, zero
`"out of stock"`, under three `"low stock"`, the rest `"sufficient"`. Each of the four
states has its own `return` line, and the four states are mutually exclusive: a single
call falls into exactly one of them.

This function was built deliberately as an example with an adjustable branch count —
four distinct branches, four distinct test cases, and exactly how many separate blocks
each case adds to coverage can be traced one by one. `Valid` (Table-Driven Tests) and
`ReduceStock` (Faking and Interface Separation) also carried similarly branched shapes;
this lesson reuses the same shape to see how the coverage tool counts this branching.

All three test sets run against the same `inventoryText` source — `Label` itself never
changes, only which states get called changes. This shows that coverage ratio is not a
property of a source but a property of a **source-test pair**: the same source's
coverage ratio can take any value between 57.1% and 100.0%, depending on which test set
asks the question.

The `Setup` and `Run` routines have not changed since the topic's first lesson; this
lesson too builds a completely empty temporary module for every test set, carrying no
trace of a previous run.

**TM18.** The same `Label` function is tested with three different test sets: `T1` tests
only the "sufficient" state, `T2` tests three states ("sufficient", "out of stock",
"low stock"), `T3` calls all four.

```go
// tools.go — setup, run, and coverage-reading routines, and the Label used throughout the topic
package main

import (
	"os"
	"os/exec"
	"path/filepath"
	"strings"
)

func Setup(files map[string]string) string {
	dir, _ := os.MkdirTemp("", "trial")
	os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module trial\n\ngo 1.24\n"), 0o644)
	for name, content := range files {
		os.WriteFile(filepath.Join(dir, name), []byte(content), 0o644)
	}
	return dir
}

func Run(dir string, extraEnv []string, args ...string) (string, bool) {
	cmd := exec.Command("go", args...)
	cmd.Dir = dir
	cmd.Env = append(append(os.Environ(), "GOTOOLCHAIN=local"), extraEnv...)
	output, err := cmd.CombinedOutput()
	return string(output), err == nil
}

func coverageRatio(output string) string {
	for _, s := range strings.Split(output, "\n") {
		if i := strings.Index(s, "coverage: "); i >= 0 {
			return strings.TrimSpace(s[i+len("coverage: "):])
		}
	}
	return "none"
}

// uncoveredBlockCount counts the blocks that never ran at all in the coverage profile.
func uncoveredBlockCount(profile []byte) int {
	n := 0
	for _, line := range strings.Split(string(profile), "\n")[1:] {
		fields := strings.Fields(line)
		if len(fields) == 3 && fields[2] == "0" {
			n++
		}
	}
	return n
}

const inventoryText = `package trial

type Item struct {
	Name     string
	Quantity int
	Price    int
}

func Label(it Item) string {
	if it.Quantity < 0 {
		return "invalid"
	}
	if it.Quantity == 0 {
		return "out of stock"
	}
	if it.Quantity < 3 {
		return "low stock"
	}
	return "sufficient"
}
`

const testTextT3 = `package trial

import "testing"

func TestLabel(t *testing.T) {
	if got := Label(Item{"pen", 5, 250}); got != "sufficient" {
		t.Errorf("got %s", got)
	}
	if got := Label(Item{"pen", 0, 250}); got != "out of stock" {
		t.Errorf("got %s", got)
	}
	if got := Label(Item{"pen", 2, 250}); got != "low stock" {
		t.Errorf("got %s", got)
	}
	Label(Item{"pen", -1, 250})
}
`
```

```go
// measurement.go — three test sets, three separate coverage ratios and uncovered block counts
package main

import (
	"fmt"
	"os"
	"path/filepath"
)

const testTextT1 = `package trial

import "testing"

func TestLabelSufficient(t *testing.T) {
	if got := Label(Item{"pen", 5, 250}); got != "sufficient" {
		t.Errorf("got %s", got)
	}
}
`

const testTextT2 = `package trial

import "testing"

func TestLabel(t *testing.T) {
	if got := Label(Item{"pen", 5, 250}); got != "sufficient" {
		t.Errorf("got %s", got)
	}
	if got := Label(Item{"pen", 0, 250}); got != "out of stock" {
		t.Errorf("got %s", got)
	}
	if got := Label(Item{"pen", 2, 250}); got != "low stock" {
		t.Errorf("got %s", got)
	}
}
`

func main() {
	fmt.Println("-- run: three separate ratios with three test sets --")
	sets := []struct{ name, text string }{
		{"T1", testTextT1},
		{"T2", testTextT2},
		{"T3", testTextT3},
	}
	for _, set := range sets {
		dir := Setup(map[string]string{"inventory.go": inventoryText, "inventory_test.go": set.text})
		output, _ := Run(dir, nil, "test", "-coverprofile=coverage.out", "./...")
		profile, _ := os.ReadFile(filepath.Join(dir, "coverage.out"))
		fmt.Println(set.name, "coverage ratio:", coverageRatio(output),
			"uncovered block count:", uncoveredBlockCount(profile))
	}
}
```

```
-- run: three separate ratios with three test sets --
T1 coverage ratio: 57.1% of statements uncovered block count: 3
T2 coverage ratio: 85.7% of statements uncovered block count: 1
T3 coverage ratio: 100.0% of statements uncovered block count: 0
```

**TM19.** The case count could also be counted from the source in the previous two
lessons, and it can be counted here too — `T1` is one call, `T2` three, `T3` four. But
the coverage ratio itself cannot be read from the source at all: while the same `Label`
function stays unchanged, the ratio climbs from 57.1% to 100.0% only because which
test set calls it changes. The profile file produced with the `-coverprofile` flag also
gives which **blocks** this ratio comes from: `uncoveredBlockCount` reads each line of
the profile and counts the ones whose last column is zero — this column is how many
times that block ran during the run. In `T1`, three blocks never run (three `return`
lines), in `T3`, none.

The coverage tool itself produces run information, because which block ran and which
did not can only be known once the tests are actually run — anyone reading the source
can see that four states exist, but cannot derive from the source alone which of these
four states a given test set triggers. This is the last appearance, in this topic, of
the distinction measured in Table-Driven Tests: one number (case count) sits in the
source, another number (coverage ratio) only emerges by running it.

It is worth noting that all three sets are run with the same routines — `Setup`,
`Run`; the only thing that changes is the content of the `inventory_test.go` file.
`Run` is called this time with the `-coverprofile` flag, and this flag tells `go test`
to write not just a ratio but the raw data behind that ratio to a file as well.
`-cover` gave only the summary ratio (the form used in the previous lesson);
`-coverprofile` records the same information block by block, and this lesson reads that
detail with the `uncoveredBlockCount` routine. The profile file itself sits temporarily
in this program's working directory too, and its path is never printed anywhere — only
the number derived from its content is read.

#### The Limiting Measurement: A Hundred Percent Coverage Does Not Show Correctness

`T3` calls all four states, and its last call —
`Label(Item{"pen", -1, 250})` — never takes or checks the return value at all. In the
coverage tool's eyes, this line is a full test: `Label` was called, the relevant block
ran, the ratio climbed to 100%. But whether this call **produced a correct result** was
never checked anywhere.

It is worth stressing that this is not a typo — anyone reading `T3`'s source sees the
fourth call, confirming that `Label`'s fourth state **was called**. What is missing is
that an `if got := ...; got != "expected"` pattern was never added to that call; this
pattern was present through the third call, and was dropped for the fourth. The coverage
tool does not notice this drop, because the question it asks is "did this line run," not
"was this line's result bound to an assertion."

This connects to the limit measured in Faking and Interface Separation: there it was
shown that a fake does not carry all the real one's assumptions; here it is shown that a
test set's own assertions do not cover all of its own branches — both from the same
root: a test's "passing" carries no guarantee about anything outside the scope of what
it tested.

**TM20.** The measurement below makes this concrete: a broken version of `Label` — one
that also labels a negative quantity as `"out of stock"`, never producing the
`"invalid"` state at all — is run with the same `T3` test set.

```go
// boundary.go — what T3, with full coverage, says about a broken source
package main

import (
	"fmt"
	"strings"
)

const brokenInventoryText = `package trial

type Item struct {
	Name     string
	Quantity int
	Price    int
}

func Label(it Item) string {
	if it.Quantity < 0 {
		return "out of stock"
	}
	if it.Quantity == 0 {
		return "out of stock"
	}
	if it.Quantity < 3 {
		return "low stock"
	}
	return "sufficient"
}
`

func main() {
	fmt.Println("-- limiting measurement: what T3, with full coverage, says about a broken source --")
	dir := Setup(map[string]string{"inventory.go": brokenInventoryText, "inventory_test.go": testTextT3})
	output, _ := Run(dir, nil, "test", "-cover", "-v", "./...")
	passed := strings.Contains(output, "--- PASS: TestLabel")
	fmt.Println("test passed:", passed, "coverage ratio:", coverageRatio(output))
}
```

```
-- limiting measurement: what T3, with full coverage, says about a broken source --
test passed: true coverage ratio: 100.0% of statements
```

**TM21.** In the broken version, a negative quantity now returns `"out of stock"`
instead of `"invalid"` — a real defect, because these two states can carry different
meanings elsewhere in the `inventory` package (an item that ran out of stock and an
item whose stock count is broken are not the same thing). But `T3` never catches this
defect at all: the test still passes, coverage is still `100.0%`. The reason is simple —
`T3`'s last line calls `Label` but does nothing at all with the value it returns; the
coverage tool sees that this line **ran**, it never asks whether the line that ran
**produced a correct value**.

The coverage ratio of both runs (correct source, broken source) is also byte-for-byte
the same: `100.0% of statements` in both. The coverage tool sees no difference at all
between the two sources, because the difference is not in whether a line ran, but in
which value a line that did run produced. This is the same limit that applies to every
piece of tool information in this topic, applied to coverage: a tool answers only the
**question it itself asks**, and the question the coverage tool asks is always the
same — did this line run — it never asks which value it produced.

This is the last form of the same limit repeated across the topic's six lessons: in
Test Package Basics a test that verified nothing still passed; here a single **call**
that verifies nothing carries the coverage ratio of a test set whose other three
assertions are sound all the way to 100%. The coverage tool counts blocks that
**ran**, not blocks that were **correctly tested** — and these two numbers, as in the
`T3` example, can look identical while saying something different. A fully covering
test set is a guarantee that every line of the source ran at least once; it is not a
guarantee that every line of the source **ran correctly**.

This has a concrete production-side counterpart: as a codebase's coverage ratio
approaches 100%, the people adding to it tend to read that ratio as a sign of
completeness. The measurement above shows this reading is incomplete — the ratio says
the lines ran, it does not say the values those lines **produced** were verified. A
codebase can misbehave even with a hundred percent coverage, because the coverage tool
itself never asks whether an assertion exists at all — it only counts whether a line ran
or not. This is the carryover, into coverage ratio, of the claim set up in Test Package
Basics that a passing test does not say it is correct: a high coverage ratio does not
state correctness either, it only states what was run.

This closing measurement shows that coverage itself is also an indicator, not a target:
reaching 100% is easy, seeing which questions are still left unanswered while reaching
it is hard. The coverage tool does not solve this difficulty, it only shows which lines
were never run at all — the rest, whether an assertion actually checks the value that
line produced, is left to the person who wrote it.

#### Summary

- When the same `Label` function was tested with three test sets, coverage ratio took
  three different values (57.1%, 85.7%, 100.0%); case count could be counted from the
  source, coverage ratio could only be learned by running it.
- The coverage profile also gives which block never ran at all; the uncovered block
  count dropped from 3 to 0 as the test set widened.
- The limiting measurement: a test set that calls all four states but never checks its
  last call's result still passed and still gave 100% coverage when run against a
  broken source.
- The coverage tool counts lines that ran, not lines that were correctly tested — this
  distinction is the last form of the same limit repeated across the topic's six
  lessons.
- The fake dependency family, the table-driven test pattern, and coverage measurement
  were built in the Unit Testing and Test-Driven Development course; this lesson only
  measured Go's own coverage tool.

#### Next Step

The same question was asked six times across this topic: a test's verdict, a case list,
an HTTP handler's response, a fake dependency's limit, which number of a benchmark is
reproducible, and now what coverage ratio counts — all were classified with a source,
tool, or run label, and each of the six lessons showed its own limiting measurement by
running it: a passing test does not say it is correct, adding a case does not raise
coverage, a test helper tests the handler and not the network, a fake implementation
does not carry all the real one's assumptions, allocation count cannot be compared
without saying what it counts, and now coverage counts lines that ran, it does not say
they were correctly tested. The next topic asks the same question of memory: a profile,
too, just like coverage, sees the path that ran and states where memory was allocated —
but it does not say why it was slow.

## Performance and Memory

What number the profile data carries, the compiler reporting in a readable form its decision on whether to allocate a value on the stack or the heap, the effect of allocation pressure on garbage collection behavior, and resolving execution traces by event count.

### Profiling

Source: https://academia.sh/en/courses/go-production/profiling

A profile is a run fact and in this lesson it is read as a number: how many values the same function's two call forms allocate. Limiting measurement: a profile sees only the path that runs — the values a branch that never executes would have allocated never appear in the profile, even if they sit written in the source.

The previous lesson showed that coverage ratio counts which lines ran, not whether they
were tested correctly. This lesson asks the same question of memory: where a running
program allocates cannot be seen by reading the source, but once a run finishes, a
profile states it **as a number**. A profile is a run fact just like coverage — someone
reading the source cannot see how many values a given function allocated; they can only
run it and ask.

This shared shape is not a coincidence: every lesson since the course opened has used
the same skeleton — first classify whether a fact stands in the source, in a tool, or
in a run, then measure the limit specific to that class. The profile's place here is
clear: like coverage, it is learned only by running. But unlike coverage, what the
profile reads is not a true/false decision but a **quantity** — how that difference
carries into the measurement is this lesson's subject.

The Concurrency course (M09/K03) already established profiling and a measurement
discipline: a measurement cannot be compared without stating what it counts. This
lesson carries that discipline forward with Go's own tools; that course's axis (the
overlapping step) is not used here. The question here is narrow: what does a profile
tell you — **where** — and what does it not tell you?

#### Three Profile Types, One Number

The catalog defines this topic with three profile types: a CPU profile, a memory
profile, and a block profile. All three can be produced with the language's own
measurement package, but the three do not carry the same kind of information. A CPU
profile samples where the program spends its time by function — by its nature this
measurement is **duration**, and duration is not written, printed, or measured in any
lesson of this course. A block profile samples similarly: it measures how long a
goroutine waits on a lock or a channel, again duration-based.

A memory profile answers a different question: **how many** values were allocated, not
**how much time** passed. This is the only profile type this course can count — its
number stays constant from one run to the next, unlike duration, which depends on the
machine and the load at that moment. This lesson therefore focuses only on the memory
profile, and on a single summary of it — the allocation count per operation. CPU and
block profiles continue to exist; they are not measured here, because what they measure
is duration.

#### Profile: How Many Values, Which Call

In this lesson the word "profile" is reduced to a single number: how many values a call
allocates per operation. This number is read **from inside the program** with the
language's own measurement package — memory bytes, duration, and stack traces are never
printed.

```go
// profile.go — the two call forms of the same function, showing how many values the profile allocates
package main

import "fmt"

type Record struct {
	Name   string
	Amount int
}

// format turns a record into a single line. When verbose is requested it
// builds an extra field slice; when it is not, that body never runs.
func format(r Record, verbose bool) string {
	line := fmt.Sprintf("%s:%d", r.Name, r.Amount)
	if verbose {
		fields := make([]string, 0, 3)
		fields = append(fields, fmt.Sprintf("name=%s", r.Name))
		fields = append(fields, fmt.Sprintf("amount=%d", r.Amount))
		fields = append(fields, fmt.Sprintf("length=%d", len(r.Name)))
		for _, f := range fields {
			line += " " + f
		}
	}
	return line
}
```

**PM1.** `format` is a single function but carries two separate paths: when `verbose`
is false, only the first line runs; when it is true, three separate formatting calls
and a concatenation loop are added. Someone reading the source sees that both paths
exist but cannot see how much each allocates — that is information no reading can
supply, only a run can.

#### Measuring the Two Call Forms

`format` itself measures nothing — it only produces a result. Measurement stands in a
separate layer: the language's own benchmark function (`func(*testing.B)`)
repeats the call `b.N` times; the measurement package counts how many values moved to
the heap during these repetitions and yields an average number per operation. The file
below defines two separate benchmark functions — one calls
`verbose=false`, the other `verbose=true` — and runs each of them five times.

```go
// measurement.go — two calls of the same function, allocation count across five runs
package main

import (
	"fmt"
	"testing"
)

var sampleRecord = Record{Name: "bolt", Amount: 12}

func profilePlain(b *testing.B) {
	for i := 0; i < b.N; i++ {
		_ = format(sampleRecord, false)
	}
}

func profileVerbose(b *testing.B) {
	for i := 0; i < b.N; i++ {
		_ = format(sampleRecord, true)
	}
}

func fiveRuns(f func(*testing.B)) (int64, int) {
	values := map[int64]bool{}
	var first int64
	for i := 0; i < 5; i++ {
		r := testing.Benchmark(f)
		values[r.AllocsPerOp()] = true
		if i == 0 {
			first = r.AllocsPerOp()
		}
	}
	return first, len(values)
}

func main() {
	fmt.Println("-- profile: how many values were allocated --")
	plain, count1 := fiveRuns(profilePlain)
	verbose, count2 := fiveRuns(profileVerbose)
	fmt.Printf("%-24s allocs=%-3d distinct values across five runs=%d\n", "plain call", plain, count1)
	fmt.Printf("%-24s allocs=%-3d distinct values across five runs=%d\n", "verbose call", verbose, count2)
	fmt.Println("difference:", verbose-plain, "allocs, belonging only to the verbose path")
}
```

```
-- profile: how many values were allocated --
plain call               allocs=2   distinct values across five runs=1
verbose call             allocs=9   distinct values across five runs=1
difference: 7 allocs, belonging only to the verbose path
```

**PM2.** `fiveRuns` applies the same pattern that holds in every performance lesson of
this course: the same measurement is repeated five times and the number of distinct
values is counted. Duration is never read here; only `AllocsPerOp` is taken from the
result `testing.Benchmark` returns. A single value appearing in all five runs shows
that this number is constant from run to run — unlike duration, it does not depend on
the environment.

`values` is kept as a `map[int64]bool`, not a single counter; the reason is to
**prove**, not assume, whether the five runs give the same value. If the five runs
give the same number, `values` carries a single key and `len(values)` comes out as
one; if a different number showed up, a second key would be added and this count
would rise to two — exactly like the duration measurement in the course's shared core
coming out different in two of eight runs and being excluded from the frame. For the
allocation count this never happens; both lines in the output below giving
`distinct values across five runs=1` confirms this claim.

#### Profiling Inside a Function, Profiling a Service

The measurement above is a scaled-down form of a profiling tool. Profiling a service
running in production measures **by sampling** how much memory which function
allocates under real traffic — the service runs for hours, the profiling tool collects
allocations throughout that time, and in the end it yields a distribution. This
distribution reflects real user input but does not come out the same from run to run:
if a day brings more verbose records, the profile changes too.

The method used in this lesson does something different: it runs a single function
with a single input five times and looks for a constant number. This does not stand in
for profiling a service — it is a small, isolated, and reproducible measurement. The
difference matters: a production profile answers the question "what is actually
happening," and its answer changes with traffic; the measurement here answers the
question "what does this function do with this particular input," and its answer is
constant. Both carry the word profile, both are run facts, but one is a repeatable
experiment and the other is a continuous observation.

This narrowing has a cost: the measurement of an isolated function does not show that
function's share inside a real service. `format` might take one percent of a request's
time, or ninety percent — this lesson never says, because the measurement severs the
function from its context. A production profile's real strength is exactly here: it
shows which function makes up how much of the **total** allocation while the whole
program runs. The method built in this lesson is a model of examining a single tile
inside that larger picture with a magnifying glass — not the larger picture itself.

#### The Difference Between Profiling and Benchmarking

A later lesson in this course also uses the same measurement tool
(`testing.Benchmark`, `AllocsPerOp`) but asks a different question: which of two
**implementations** of the same work allocates less. This lesson does not ask that
question — here there is a single function, `format`, and its two call forms separate
its **two branches**. Benchmarking compares two separate implementations;
profiling shows where a single implementation allocates. The tool is the same, the
question is different: one asks "which is better," the other asks "where." This
distinction matters because the same number (`AllocsPerOp`) shows up in both contexts,
and which question it answers can only be told from how the measurement is set up.

#### Reading the Numbers

The `plain` call allocates two values: the `fmt.Sprintf("%s:%d", ...)` call itself
produces a string, and in doing so moves at least one intermediate value to the heap.
The `verbose` call allocates nine values. The seven allocations in between belong
entirely to the `if verbose` block: three separate `fmt.Sprintf` calls, one slice
allocation, and part of the concatenations inside the loop. In the source, both paths
sit side by side in the same function body; without a profile, no one can tell which
one allocates how much.

This makes clear the question a profile answers: **where** allocation happens. Which
call form allocates more values is read directly from the measurement. But a profile
does not say **why** something is slow — a question never measured in this lesson,
because answering it requires looking at duration, and duration is not written in any
lesson of this course. A high allocation count does not mean the program runs slowly;
it only means that path requests more memory. The two usually move together, but the
profile itself does not separate them — the person reading the allocation count does.

The plain call's two allocations are themselves a lesson in reading. The source shows
a single `fmt.Sprintf` call, but this one line hides more steps than it appears to:
`Sprintf` produces a new string, and while doing so, it also wraps an integer like
`r.Amount` into an interface value as it passes it to a variadic parameter (`...any`).
Even though the source shows no pointer, no `new`, no `append`, exactly these two
steps — string production and wrapping into an interface — lead to two separate
allocations. Which value gets wrapped this way and when is a consequence of the
language's own calling convention and a decision made by escape analysis. That
decision itself is the subject of the next lesson; this lesson only counts how many
values were allocated, not which decision led to it.

The course's opening split eleven facts into source, tool, and run; this lesson uses
only the run out of those three classes, because a profile's number can only be
learned by running.

#### Limiting Measurement: The Path That Does Not Run Is Absent From The Profile

In the table above, the `plain` row shows two values — **none** of the three
`Sprintf` calls, the slice allocation, or the concatenation loop enter this row. The
reason is not that these values are small or that the compiler optimizes them away:
when `verbose` is passed false, the `if` block's **body never runs at all**. The
source holds code corresponding to seven allocations, but `profilePlain` never
triggers it; a profile cannot see anything that never triggered.

**PM3.** This is the limit profiling shares with coverage ratio (the previous
lesson): both measure only the path that **runs**. Coverage ratio did not count an
untested line; a profile does not count an untriggered branch's allocation. Before
concluding that a branch "allocates little," one must be certain that branch was
actually triggered — otherwise what is measured is not that branch's behavior, but
the fact that it never ran. The parallel in form is also worth noting: coverage ratio
ignored an untested line by counting it as zero, and a profile ignores an untriggered
branch's allocation by counting it as zero too — both write "absent" and
"unmeasured" with the same symbol (`0`), and it is up to the reader to tell the two
apart.

This limit works in two directions in practice. On one hand, a rarely triggered
branch (say, a code path entered only on an error) may never show up in daily
profiles and go unnoticed. On the other hand, when comparing two call forms, one must
be sure the input used actually triggers both branches — otherwise the comparison is
looking at the wrong path. In the measurement above, `profilePlain` calls only with
`verbose=false`, and `profileVerbose` calls only with `verbose=true` — this choice is
deliberate, because the comparison's purpose is exactly to see these two branches
separately.

The same limit holds inside a single call form too. `format` was called with only two
cases: `verbose` either entirely true or entirely false. A real set of callers might
call this function with a mixed distribution — a tenth of the records verbose, the
rest not — and the average allocation count that distribution produces would equal
neither of the two extremes above. A profile describes the input it was measured
with; a different input distribution's profile produces a different table. This is
why a profile number cannot be carried without stating which input produced it — just
as the previous lesson's coverage ratio cannot be carried without stating which test
set produced it.

#### What The Number Must Not Decide

A two-line table alone does not propose an action. The `verbose` call allocating
seven more values does not mean the `verbose` branch should be removed — that branch
probably serves a real need, and formatting three separate fields individually may
genuinely be used somewhere else. The profile number only shows **where** allocation
happens; deciding whether that allocation is necessary requires knowing which caller
uses which branch at what frequency, something the number does not address. This is a
warning the next lesson will repeat too: a number getting smaller is not
automatically an improvement, because the number alone does not say what it got
smaller in exchange for.

This repeats a pattern that has held since the course's first lesson: the
information a tool states is limited to **what the tool measures**. The coverage
tool only stated which line ran, not whether it was correct; the profiling tool only
states which call allocated how many values, not whether that allocation is needed.
In both cases, a reading that goes past the limit does not come from the tool
itself, but from the reader forgetting the tool's limit.

#### Summary

- A profile is a run fact: how many values a given call allocates is learned only by
  running it, not by reading the source.
- The same function's two call forms (`plain`, `verbose`) were measured with
  `AllocsPerOp`; the verbose call allocated seven more values and this difference did
  not change across five runs.
- A profile answers **where** allocation happens, not **why** something is slow —
  duration is not measured in any lesson of this course.
- The values a branch that never runs would have allocated never appear in a profile;
  code sitting in the source does not change this, only that path actually running
  does.
- This limit matches the previous lesson's coverage ratio limit in shape: both
  measure only the path that runs.

#### Next Step

A profile states where a value was allocated after the run. But the decision of
whether a value will sit on the stack or the heap is made long before the run, while
the source is merely being compiled — this lesson saw the **result** of that decision
as a number, never the decision itself. The next lesson looks at that decision's
mechanism: how the compiler reports this decision, which patterns move a value to the
heap, and how a small change to the same function changes the decision.

### Escape Analysis

Source: https://academia.sh/en/courses/go-production/escape-analysis

Escape analysis is a tool fact: the compiler decides whether to allocate a value on the stack or the heap and reports this without a run. Three of four functions move to the heap, and the same function changes this decision with a small change. Limiting measurement: this decision is not a promise of the language, it is an implementation detail tied to the compiler's version.

A profile stated where a value was allocated **after** the run, as a number. But the
decision of whether that value will sit on the stack or the heap is made long before
the run — while the source is merely being compiled, not yet executed. This lesson
looks at that decision itself: how the compiler reports this decision, and how a small
change in the same function's body changes it.

This is the clearest **tool** example of the source/tool/run distinction that has
held since the course opened. Escape analysis is not written in the source — the word
`heap` or `stack` appears nowhere; the code only defines a variable and uses it. But
the compiler does not **run** the program to make this decision either; it makes the
decision during compilation and reports it on request. This differs from a run fact:
a run fact (like the previous lesson's profile number) only appeared once the
program was executed, and it was separately tested for staying the same across runs.
A tool fact, by contrast, requires no run — the same source, with the same compiler,
gives the same decision every time.

#### Stack, Heap, and the Decision Between Them

The question of whether a value sits on the stack or the heap is not new in this
course: stack and heap were established as part of memory layout in the How
Computers Work course (M01/K01), and are not repeated here. That course established
this distinction — the stack is a fast, sequential region that opens and closes
together with a call frame; the heap is a region independent of the call frame,
managed by hand (or, as this lesson will show, by the compiler's decision). This
lesson does not measure what those two regions **are**, but who decides **which**
one a value goes to.

The decision itself can be summarized with a simple rule: if a value's lifetime
outlasts the lifetime of the call frame that created it, the value must move to the
heap — otherwise the value would be lost when the call frame closes. The compiler
decides this by tracing the source code itself — following where a pointer goes,
which interface it is passed to, which container it is appended to. This tracing
step is **escape analysis**: the compilation step that determines whether a value
"escapes" the scope that created it.

The difference between stack and heap is not only "where it sits" but "who manages
it." A value on the stack has its lifetime automatically closed together with the
call frame — no separate cleanup step is needed. A value on the heap has a lifetime
independent of the call frame, and it is the runtime, not the program, that decides
when this value gets removed. This second question — how a value moved to the heap
eventually gets cleared out — is the next lesson's subject; this lesson only looks
at whether the value goes to the heap **or not**.

In the previous lesson, the `verbose` branch of the `format` function allocated
seven extra values, and in this lesson where those seven allocations **come from**
becomes clear: each of the three `fmt.Sprintf` calls made exactly the kind of escape
decision this lesson will examine while wrapping the string it produced and its
arguments into an interface. The profile counted the **result** of those decisions
(how many values); this lesson reads the decision **itself**.

#### Reading the Decision on Its Own

The way to read escape analysis's decision is to call the compiler **from inside the
program** and read only its decision, not its text. The helper function below does
this: it writes a given source fragment on its own into a temporary module, runs the
compiler on that module, and searches the output for whether a move-to-heap
notification exists — the notification's **text** is never printed, only this truth
value is.

```go
// escape.go — reads the compiler's move-to-heap decision from inside the program, without printing its text
package main

import (
	"os"
	"os/exec"
	"strings"
)

// escapes writes the given body on its own into a package and runs the
// compiler on that package. The returned value is only a truth value;
// the compiler's notification text is never read or printed.
func escapes(body string) bool {
	dir, err := os.MkdirTemp("", "escape")
	if err != nil {
		return false
	}
	defer os.RemoveAll(dir)
	os.WriteFile(dir+"/go.mod", []byte("module escape\n\ngo 1.24\n"), 0o644)
	os.WriteFile(dir+"/value.go", []byte(body), 0o644)
	cmd := exec.Command("go", "build", "-gcflags=-m -l", "-o", os.DevNull, ".")
	cmd.Dir = dir
	cmd.Env = append(os.Environ(), "GOTOOLCHAIN=local")
	output, _ := cmd.CombinedOutput()
	text := string(output)
	return strings.Contains(text, "moved to heap") || strings.Contains(text, "escapes to heap")
}
```

**PM4.** `escapes` never calls the target function — it only defines it. The
compiler resolves a function's body during compilation and reports its decision even
when no code calls it; this is why `func main() {}` can stay empty in `escapes`'s
temporary module. This shows the decision depends on **definition, not being
called** — escape analysis requires no run, because there is nothing running; there
is only something being compiled.

Compiling each source **on its own**, in a separate package, is also a deliberate
choice. The compiler can inline some small functions at their call site; when such
inlining happens, the escape decision is written not to the function's own body but
to the call site it was inlined into, and it becomes harder to read. Compiling each
source only in its own package, without mixing into any other call, removes this
complication from the start: where the decision gets written and which function it
belongs to never falls into ambiguity.

This isolation has a cost too: in a real program, if a function like
`returnsAddress` gets inlined at its call site, its decision may come out
**different** from what is measured here — if the caller uses the result
immediately and discards it, the compiler may still manage to keep the value on the
stack after inlining. This lesson does not measure that situation; it measures only
the decision the function gives **on its own**, without being inlined into any
call. The decision in a real program can therefore come out more optimistic than
what is seen here, never more pessimistic — inlining can only **prevent** an
escape, it never adds a new one.

#### Four Functions, One Decision Table

The measurement below defines four separate functions, all four using the same
`Record` struct, but all four handling the value in a different way: one only reads
the value locally, one returns its address, one gives it to an interface, one
appends it to a slice. A fifth source is nearly identical to the first one
(`staysLocal`) — the only difference is that the last line returns an address
instead of a value.

```go
// measurement.go — four functions, the compiler's move-to-heap decision, and a small change
package main

import "fmt"

const staysLocal = `package main

type Record struct{ Name string }

func staysLocal() int {
	k := Record{Name: "bolt"}
	return len(k.Name)
}

func main() {}
`

const returnsAddress = `package main

type Record struct{ Name string }

func returnsAddress() *Record {
	k := Record{Name: "nut"}
	return &k
}

func main() {}
`

const givenToInterface = `package main

import "fmt"

type Record struct{ Name string }

func givenToInterface(k Record) {
	fmt.Println(k)
}

func main() {}
`

const appendedToSlice = `package main

type Record struct{ Name string }

func appendedToSlice(target []*Record) []*Record {
	k := Record{Name: "nail"}
	return append(target, &k)
}

func main() {}
`

const staysLocalChanged = `package main

type Record struct{ Name string }

func staysLocalChanged() *string {
	k := Record{Name: "bolt"}
	return &k.Name
}

func main() {}
`

func main() {
	rows := []struct {
		name   string
		source string
	}{
		{"value staying local", staysLocal},
		{"value returning an address", returnsAddress},
		{"value given to an interface", givenToInterface},
		{"value appended to a slice", appendedToSlice},
		{"same function, small change", staysLocalChanged},
	}
	escaped := 0
	fmt.Println("-- compiler's decision to move to heap --")
	for i, s := range rows {
		moved := escapes(s.source)
		if moved && i < 4 {
			escaped++
		}
		fmt.Printf("%-32s moved to heap=%t\n", s.name, moved)
	}
	fmt.Println("moved to heap out of four functions:", escaped, "/ 4")
}
```

```
-- compiler's decision to move to heap --
value staying local              moved to heap=false
value returning an address       moved to heap=true
value given to an interface      moved to heap=true
value appended to a slice        moved to heap=true
same function, small change      moved to heap=true
moved to heap out of four functions: 3 / 4
```

#### Reading the Numbers

**Three** of the four functions move to the heap, one does not. `staysLocal` only
reads the value inside its own body (`len(k.Name)`) and gives nothing outward — the
value can close together with the call frame, it stays on the stack. The other
three carry the value **outward** by three separate paths: `returnsAddress` returns
the value's address, `givenToInterface` passes the value into an interface
parameter (`fmt.Println`'s variadic `...any` parameter), `appendedToSlice` appends
the value's address to a slice. All three share one thing in common: the value
**must outlive** the call frame that created it — someone may still access that
value after the call ends. The compiler sees this and moves the value to the heap,
making it independent of the call frame's lifetime.

The fifth row shows this more sharply: `staysLocalChanged` is nearly identical to
`staysLocal` — same struct, same field name, same first line. The only difference
is in the last line: `return &k.Name` instead of `return len(k.Name)`. This single
change turns the returned value from an integer into an **address**, and flips the
decision from `false` to `true`. The source shows no new pointer type, no explicit
memory allocation call — only an `&` was added. The escape analysis decision looks
not at the code's **intent**, but at the value's **reachability**; taking a
field's address means the struct holding that field can no longer stay inside the
call frame.

This asymmetry explains why staying on the stack is counted a **gain**, not the
**default**. Keeping a value on the stack requires writing nothing special —
merely not carrying the value outward is enough. But this "not carrying outward"
is fragile: appending to a slice as in `appendedToSlice`, passing to an interface
as in `givenToInterface`, or taking a single field's address as in
`staysLocalChanged` — all four push in the same direction: they push the value
from the stack to the heap. There is no operation in the reverse direction — a
single line is enough to "force" a value to the heap, while rescuing it from the
heap requires the code to never give that value outward at all. This is why three
of four functions escaping is not a coincidence specific to this example set:
every operation that gives outward pushes in the same direction.

The course's opening split eleven facts into source, tool, and run; this lesson
uses only the tool out of those three classes — the escaping value count is
neither written in the source nor learned only by running; the compiler derives
and reports it **without a run**.

`escapes` returning a single truth value hides the fact that the compiler actually
gives **two separate kinds** of notification. In a function like `returnsAddress`,
the notification says a local value was moved; in a function like
`givenToInterface`, the notification says a **parameter** itself "leaks" into the
caller's scope, because the value is already produced by the caller and passed in
as a parameter. Both end up in the same place — the value sits on the heap — but
the path differs: one carries a value that forms **inside** the function, the
other carries a value that comes **from outside** into the function. This lesson's
measurement merges the two under a single `true`/`false` because their shared
outcome — moving to the heap — is the question this course asks; the detail of the
path belongs to the compiler's internal workings and is not read here.

#### Limiting Measurement: This Is Not a Promise, It Is a Decision

Someone reading the table above might quickly arrive at a rule like "in Go, a value
goes to the heap if its address is taken." This rule holds true for today's
compiler, but it is not the language's **promise**. The language specification
nowhere says "a value whose address is taken goes to the heap"; it only guarantees
how long a value will live, not **where** it sits. Escape analysis is a **method**
of fulfilling this guarantee — an implementation decision a particular compiler
makes in a particular version.

This has a concrete consequence: a smarter compiler could manage to keep a
function like `returnsAddress` on the stack too — for example, if the caller uses
the result immediately and discards it, and the compiler can prove this. This
lesson did not see that proof, because none of the four functions in this lesson
permit such a proof — all of them genuinely carry the value outside the call
frame. But the same source, compiled with a different compiler version, may give a
different decision; this lesson therefore presents the decision table not as a
**fact**, but as an **observation** of this particular compilation.

The same uncertainty is an example of the general distinction between a
language's **specification** and its **implementation**. The specification
guarantees how long a value will live and which operations are valid; the
implementation chooses which memory region and which method holds this guarantee.
The same distinction holds for other compiler decisions too — a map's iteration
order, which carries no ordering guarantee, was likewise an implementation
choice, not the language's promise. Escape analysis is this general pattern
applied to memory layout: the decision you see is correct, but that decision
belongs to the compiler, not the language.

This limit comes down to the same root as the previous lesson's limit: a tool
fact is limited by the tool itself. The profiling tool only saw the path that
ran; the escape analysis tool only shows **this compiler's this decision**.
Neither gives the truth — both give the tool's answer at that moment.

This has a practical consequence: a comment written in a codebase saying "this
value escapes to the heap, fix it" may turn out wrong once the compiler changes.
The escape decision is not a fact stored together with the source; it is an
output reproduced at compile time. This is why the table in this lesson is not a
one-time thing either — the same `escapes` call re-runs the compiler and asks the
decision again every time it executes; no decision is stored from a previous run.

#### Summary

- Escape analysis is a tool fact: the compiler decides whether a value will be
  allocated on the stack or the heap without ever running the program, and
  reports this without a run.
- Three of four functions moved to the heap — the one returning the value's
  address, the one giving it to an interface, and the one appending it to a
  slice. The function that keeps the value only in its own body stayed on the
  stack.
- A one-line change to the same function (`&k.Name` instead of `len(k.Name)`)
  flipped the decision from `false` to `true` — escape analysis looks at the
  value's reachability, not the code's intent.
- This decision is not a promise of the language, it is an implementation detail
  tied to the compiler's version; a different compiler version may give a
  different decision for the same source.
- The concepts of stack and heap were not re-established here; this lesson only
  measured the compiler's decision between the two.
- The escape decision works in only one direction: every new path that carries a
  value outward and pushes it to the heap increases escaping; no operation alone
  calls a value back from the heap.

#### Next Step

This lesson asked whether a value **moved to the heap or not** — a one-time
decision made at compile time. But in a program's real running process, this
decision alone is not enough: every value moved to the heap must eventually be
cleared out of it too, and this clearing happens not at compile time but during a
run. The next lesson looks at this second half — how much the "moves to the
heap" decision built in this lesson adds up to in total, how certain techniques
lower that total, and why a number going down does not always mean an
improvement.

### Garbage Collection Behavior

Source: https://academia.sh/en/courses/go-production/garbage-collection-behavior

The reduction of allocation pressure is measured with a number: preallocation, buffer reuse, and the choice between value and pointer carry the same work's allocation count from four to one, from four to zero, from one to sixty-five. Limiting measurement: a number going down is not always an improvement — a style that harms readability can reach the same number too.

The previous lesson measured whether a value **moves to the heap or not** as a
one-time decision made at compile time. But a program runs not once but thousands of
times, and every value that goes to the heap must eventually be cleared out of it
too. This lesson questions the load of that clearing: how many values in total do
different styles performing the same work send to the heap, and how does that number
go down with certain techniques?

Garbage collection itself — reachability, the root set, the mark-and-sweep steps,
the fact that a pause is proportional to the live objects — was established as a
concept in the Operating System Concepts course (M01/K05), and is not repeated here.
This lesson measures not the theory but the **result**: sending fewer values to the
heap means less work for the collector. Pause duration, the amount of memory
reclaimed, and generation size are never written in this lesson — all depend on the
environment; the only thing counted is the **allocation count**.

This narrowing's rationale comes from the earlier course: that course established
that the collector's work is proportional to live objects — the more objects sit on
the heap, the longer scanning it takes. This lesson looks at that relationship's
input end: if a program sends **fewer** objects to the heap, there is less to scan.
The connection is indirect — this lesson never measures the pause — but the
direction is clear: if the allocation count goes down, the collector's work tends to
go down too. What this lesson measures is that decrease's input side; how the
collector processes it is the earlier course's subject.

#### Allocation Pressure: Three Techniques, One Measurement

"Allocation pressure" describes how many new values a program sends to the heap per
unit of time. If pressure is high, the collector has to run more often; if it is
low, more rarely. This lesson measures pressure not through duration but through
**count**: how many values a single run of the same work allocates. Three
techniques lower this number — preallocation, buffer reuse, value/pointer choice —
and a fourth example shows that this number alone is not enough.

All four share the same measurement pattern: a function is defined, a comparative
measurement function that calls that function is written, and this measurement is
run five times, counting how many distinct values come out. Unlike the previous two
lessons, here it is not a single value but four separate comparisons standing side
by side — each showing a before and after with its own pair (raw/preallocated,
new/reused, value/pointer).

```go
// measurement.go — the effect of three techniques on the allocation count, across five runs
package main

import (
	"fmt"
	"strconv"
	"testing"
)

type Record struct {
	Name   string
	Amount int
}

const recordCount = 64

var sumSink, lengthSink, recordSink int

// --- a) preallocation ---

//go:noinline
func buildBatch(n int) []int {
	var d []int
	for i := 0; i < n; i++ {
		d = append(d, i)
	}
	return d
}

//go:noinline
func buildBatchPrealloc(n int) []int {
	d := make([]int, 0, n)
	for i := 0; i < n; i++ {
		d = append(d, i)
	}
	return d
}

func benchBatchRaw(b *testing.B) {
	for i := 0; i < b.N; i++ {
		sumSink += len(buildBatch(recordCount))
	}
}
func benchBatchPrealloc(b *testing.B) {
	for i := 0; i < b.N; i++ {
		sumSink += len(buildBatchPrealloc(recordCount))
	}
}

// --- b) buffer reuse ---

//go:noinline
func buildLine(n int) []byte {
	var buf []byte
	for i := 0; i < n; i++ {
		buf = append(buf, strconv.Itoa(i)...)
		buf = append(buf, ' ')
	}
	return buf
}

//go:noinline
func buildLineReused(buf []byte, n int) []byte {
	buf = buf[:0]
	for i := 0; i < n; i++ {
		buf = append(buf, strconv.Itoa(i)...)
		buf = append(buf, ' ')
	}
	return buf
}

var sharedBuffer = make([]byte, 0, 8)

func benchBufferNew(b *testing.B) {
	for i := 0; i < b.N; i++ {
		lengthSink += len(buildLine(16))
	}
}
func benchBufferReused(b *testing.B) {
	for i := 0; i < b.N; i++ {
		sharedBuffer = buildLineReused(sharedBuffer, 16)
		lengthSink += len(sharedBuffer)
	}
}

// --- c) value vs pointer choice ---

//go:noinline
func buildValueSlice(n int) []Record {
	d := make([]Record, 0, n)
	for i := 0; i < n; i++ {
		d = append(d, Record{Name: "x", Amount: i})
	}
	return d
}

//go:noinline
func buildPointerSlice(n int) []*Record {
	d := make([]*Record, 0, n)
	for i := 0; i < n; i++ {
		d = append(d, &Record{Name: "x", Amount: i})
	}
	return d
}

func benchValueSlice(b *testing.B) {
	for i := 0; i < b.N; i++ {
		recordSink += len(buildValueSlice(recordCount))
	}
}
func benchPointerSlice(b *testing.B) {
	for i := 0; i < b.N; i++ {
		recordSink += len(buildPointerSlice(recordCount))
	}
}

// --- d) convoluted style: reaching the same number another way ---

//go:noinline
func buildBatchConvoluted(n int) []int {
	capacity := 0
	for counter := 0; counter < n; counter++ {
		capacity++
	}
	d := make([]int, 0, capacity)
	for i := 0; i < n; i++ {
		d = append(d, i)
	}
	return d
}

func benchBatchConvoluted(b *testing.B) {
	for i := 0; i < b.N; i++ {
		sumSink += len(buildBatchConvoluted(recordCount))
	}
}

func fiveRuns(f func(*testing.B)) (int64, int) {
	values := map[int64]bool{}
	var first int64
	for i := 0; i < 5; i++ {
		r := testing.Benchmark(f)
		values[r.AllocsPerOp()] = true
		if i == 0 {
			first = r.AllocsPerOp()
		}
	}
	return first, len(values)
}

func line(name string, f func(*testing.B)) {
	allocs, distinctValues := fiveRuns(f)
	fmt.Printf("%-30s allocs=%-3d distinct values across five runs=%d\n", name, allocs, distinctValues)
}

func main() {
	fmt.Println("-- allocation pressure: how technique changes the allocation count --")
	line("unpreallocated slice", benchBatchRaw)
	line("preallocated slice", benchBatchPrealloc)
	line("new buffer each call", benchBufferNew)
	line("reused buffer", benchBufferReused)
	line("value slice", benchValueSlice)
	line("pointer slice", benchPointerSlice)
	line("convoluted style (same number)", benchBatchConvoluted)
}
```

```
-- allocation pressure: how technique changes the allocation count --
unpreallocated slice           allocs=4   distinct values across five runs=1
preallocated slice             allocs=1   distinct values across five runs=1
new buffer each call           allocs=4   distinct values across five runs=1
reused buffer                  allocs=0   distinct values across five runs=1
value slice                    allocs=1   distinct values across five runs=1
pointer slice                  allocs=65  distinct values across five runs=1
convoluted style (same number) allocs=1   distinct values across five runs=1
```

**PM5.** All seven functions are marked with `//go:noinline` — the language's own
compilation directive, telling the compiler not to inline this function at its call
site. The reason is the same as the previous lesson: if a function is inlined at its
call site, the compiler may see that the caller uses the returned value immediately
and discards it, and this can hide the allocation behavior the comparison wants to
measure. The directive ensures the four techniques' allocation counts are measured
**inside their own body**, without mixing into any caller.

`sumSink`, `lengthSink`, and `recordSink` stand as three package-level variables,
and each benchmark function writes its own returned value into one of
them. The purpose of this is to "use" the result: if a function's return value is
never written anywhere, the compiler may treat the entire call as unnecessary and
remove it, and then what gets measured would not be the function itself but an
empty loop. Writing the result into a package removes this risk — the call
genuinely happens and genuinely allocates.

#### Reading the Numbers

**Preallocation (a):** `buildBatch` reaches 64 elements by appending to an empty
slice one at a time; the slice grows every time its capacity fills, and each growth
is a reallocation. This growth triggers four times. `buildBatchPrealloc` appends
the same 64 elements to a slice opened from the start at the right capacity with
`make([]int, 0, 64)` — growth never triggers; the single allocation belongs only to
the first `make` call. The drop from four allocations to one shows that growth is
unnecessary in cases where the capacity is **known in advance**.

**Buffer reuse (b):** `buildLine` opens a `[]byte` starting from zero on every call
and grows it four times by appending 16 pieces to it. `buildLineReused` instead
shrinks a single buffer stored at the package level to zero length with `buf[:0]`
on every call and refills it; since the capacity has already reached a sufficient
size from previous calls, a new allocation is never needed. The drop from four to
zero is sharper than in the previous technique — because here it is not only the
growth count that disappears, but **the first allocation on every call** too.

This zero count has a precondition: the buffer's capacity is not exceeded on any of
the successive calls. `sharedBuffer` opens with an eight-byte capacity but grows on
its own during the first few calls, because `buildLineReused` also uses `append`,
and growth is unavoidable when capacity falls short. Zero coming out in all five
runs shows that this growth happens **once, on the first call**, and never repeats
after that — once capacity reaches a sufficient size, every call of the same size
can reuse it. If the input size varied (sometimes 16, sometimes 1600 pieces),
growth would trigger again and again and the drop to zero would disappear.

**Value/pointer choice (c):** `buildValueSlice` keeps 64 `Record` values in a
**single** contiguous slice — all elements are part of the same allocation, the
total allocation count is one. `buildPointerSlice` instead sends each element to
the heap separately with `&Record{...}`: one allocation for the slice itself,
sixty-four allocations for each of the elements — sixty-five in total. The
difference between the two is much larger than in the earlier techniques, because
what changes here is not the growth pattern but **how many separate objects**
exist.

This difference connects directly to the previous lesson: the `&Record{...}` style
is the same pattern as `returnsAddress`, measured in the previous lesson — taking a
value's address pushes it to the heap. The difference here is scale: the previous
lesson measured a single value's fate; here the same decision repeats sixty-four
times and its effect compounds. Choosing a pointer slice instead of a value slice
is a decision commonly seen in real codebases — usually to avoid copying large
structs. This measurement shows that gain has a cost: avoiding copying raises the
allocation count. Which cost is worth which gain is a question this lesson cannot
measure — it depends on factors like the struct's size, how often it is copied,
and how many references remain, and it varies from case to case.

#### What The Three Techniques Have In Common

There is a shared idea behind all three techniques: **allocating large enough once
produces less work than allocating small many times**. Preallocation applies this
when the capacity is known in advance; buffer reuse applies this by learning the
capacity once and reusing it again and again; the value slice applies this by
keeping elements contiguous instead of separate. All three push in the same
direction — reducing the count of separate allocations — but their preconditions
differ: one requires the capacity to be **known in advance**, one requires the
buffer to be **shareable**, one requires the elements to be able to **stay
contiguous**. If one of these preconditions does not hold, the technique cannot be
applied — if the capacity is not known in advance, what to write into `make` stays
uncertain; if the buffer is accessed concurrently, sharing it leads to a race
condition; if elements are held with different lifetimes, a contiguous slice holds
them together unnecessarily.

#### Limiting Measurement: A Number Going Down Is Not Automatically An Improvement

The fourth row, `buildBatchConvoluted`, reaches the **same** number as
`buildBatchPrealloc` — one. But the two functions' bodies are not the same:
instead of taking the capacity directly as `n`, `buildBatchConvoluted`
**recomputes** it with a separate counter loop that runs `n` times and writes the
result into a variable named `capacity`. This loop produces no new information —
`capacity` always comes out equal to `n` — but it makes someone reading the source
ask "why is there a separate computation, is `n` not enough."

**PM6.** This is one of the course's most important warnings: two styles reached
the same allocation count, but one is readable and one is not. The allocation
count alone does not say which code is **better** — it only says how many values
went to the heap. The unnecessary loop in `buildBatchConvoluted` added no
allocation, because it only operates on an integer (integers of this size stay on
the stack); but it harmed readability and cost the reader's time. When a number
goes down or stays constant, the question that must be asked is "what did this
change come at the cost of" — not only "what did the number become."

This limit connects to the limit in the escape analysis lesson too: the decision
of whether a value goes to the heap was the compiler's decision, not the code's
intent. A similar distinction holds here — code written to lower the allocation
count can genuinely lower the number, but this does not explain **why** it went
down. Someone reading `buildBatchConvoluted` can only understand that the low
number comes from `make` being called with the right capacity, and that the loop
itself serves no purpose, by tracing the code line by line — the allocation count
itself does not make this distinction.

This example is not made up; it is a scaled-down form of a pattern seen in real
codebases: a developer makes a change to lower the allocation count, the number
genuinely goes down, and the change is counted "successful" — but which **part**
of the change lowered the number is never questioned. `buildBatchConvoluted`
embodies this trap: whether the unnecessary loop stays or is removed, the
allocation count stays at one, because what determines the number is `make`'s
capacity argument, not the loop. The measurement tool does not make this
distinction; the person who does is the one reading the code and tracing which
line corresponds to which number.

#### Summary

- Preallocation lowered the allocation count from four to one: if the capacity is
  known in advance, the slice's growth steps are unnecessary.
- Buffer reuse lowered the allocation count from four to zero: zeroing and
  refilling a shared buffer does not require a new allocation on every call.
- The value slice stayed at one allocation, the pointer slice rose to sixty-five:
  opening a separate object for each element is far more expensive than keeping
  them in a single contiguous store.
- The same allocation count (one) was reached two different ways — one a readable
  style that calls `make` directly with the right capacity, the other an
  unreadable style that adds an unnecessary computation.
- A falling or constant allocation count alone is not proof of an improvement;
  which change affects the number must be read from the code itself. The
  allocation count is a **run** fact — all three styles were told apart only by
  running them.
- The three techniques share the same basis: allocating large enough once
  produces less work than allocating small many times; each technique cannot be
  applied without its own precondition (known capacity, a shareable buffer,
  elements that can stay contiguous).

#### Next Step

This lesson stayed limited to the information of counting how many values a
function allocates over how many calls — only a program's memory dimension. But a
real program's runtime contains more than this: which goroutine started when, how
many ran concurrently, which event happened in which order — none of this is
captured by an allocation count. The next lesson looks at this broader record —
which events an execution trace counts, and how the event counts produced by two
different setups are compared.

### Tracing

Source: https://academia.sh/en/courses/go-production/tracing

An execution trace is a run fact and here it is read as an event count: two setups processing the same six records, one sequential and one distributed across three workers, produce the same number of record processing events but a different number of worker start events. Limiting measurement: collecting a trace changes the program's behavior, so the numbers are read with that in mind.

The previous lesson stopped at counting **how many values** a program allocates —
only a program's memory dimension. But a program's runtime contains more than
this: which goroutine started when, how many ran concurrently. This lesson looks
at this broader record: the language's own execution trace records the events
that happen during a program's run, and this lesson reads that record **as an
event count**.

All four lessons of this topic proceeded the same way: first classify where a
fact comes from (source, tool, run), then measure the limit specific to that
class. Profile and the escape decision were two separate classes — one a run,
one a tool. Allocation count was a run fact again, but it looked at four
comparisons rather than a single number. This last lesson returns to a run, but
what it asks is a kind never asked before: **how many separate units of
execution** actually ran.

The execution trace is also an example of the distinction that has held since
this course's first lesson — a run fact: someone looking at the source cannot
see how many goroutines actually ran, they can only run it and ask. But the
number read in this lesson captures something different from a profile or an
allocation count: not memory, but **concurrency itself**. This course's four
measurement axes (M09/K03) are not used here; this lesson only compares the
event counts the two setups produce.

The word "tracing" is used here in a narrow sense: the language's own execution
trace mechanism, not a profile or a log. A profile asked "how many values were
allocated"; a log (another mechanism not separately covered in this course)
holds the messages the application itself writes. An execution trace differs
from both: it is a mechanism through which the runtime itself records which
execution unit it ran at which moments. This lesson measures only the part of
that mechanism that uses application-specific markers.

#### Why A Tagged Count, Not A Raw Number

An execution trace does not only record the work the program writes; it also
records the runtime's own background work — how the scheduler distributes a
thread, when the garbage collector starts a scan. This background activity
depends on the machine's load at that moment and the operating system's
scheduling, and it **changes from run to run**. This is why a trace's **total**
event count — if background activity is counted along with everything the
program itself writes — can come out different across two separate runs, even
with the same source code.

This lesson does not measure this variability, it **avoids** it: instead of the
total count, it counts how many times two tags it chose itself
(`record-process`, `worker-start`) appear. These tags appear only in this
program's own code and never show up in background activity — this is why,
unlike the total event count, these two numbers stay **constant** from run to
run. The course's stability pattern applies here too: a measured number does
not enter the table before it is proven deterministic first.

This choice is a direct continuation of the search for reproducibility in the
previous three lessons: the profile number was counted usable because it gave a
single value across five runs, and the allocation count passed the same test.
The execution trace cannot pass this test on its own — in its raw form it
changes from run to run — but **one part** of the trace, the part it tags
itself, does. This lesson therefore measures not the whole trace, but the part
of it that comes out stable.

#### Reading The Trace With Tags

An execution trace's raw content carries everything forbidden to print in this
course: goroutine numbers, timestamps, stack traces. This lesson never prints
this content; instead, it searches the trace for the two tags **it chose
itself** and counts only how many times these tags appear.

```go
// trace.go — creates the trace file and counts how many times a tag appears in it
package main

import (
	"context"
	"os"
	"os/exec"
	"runtime/trace"
	"strings"
)

const recordTag = "record-process"
const workerStartTag = "worker-start"

// recordTrace records the given work into a trace file and returns its path;
// the path is never printed, it is only used inside this function.
func recordTrace(f func(ctx context.Context)) string {
	dir, _ := os.MkdirTemp("", "trace")
	path := dir + "/trace.out"
	file, _ := os.Create(path)
	trace.Start(file)
	f(context.Background())
	trace.Stop()
	file.Close()
	return path
}

// eventCount hands the trace file to the trace tool and counts only how many
// times the requested tag appears. The tool's output text is never printed.
func eventCount(tracePath, tag string) int {
	cmd := exec.Command("go", "tool", "trace", "-d=parsed", tracePath)
	cmd.Env = append(os.Environ(), "GOTOOLCHAIN=local")
	output, _ := cmd.CombinedOutput()
	return strings.Count(string(output), tag)
}
```

**PM7.** `eventCount` reads the text the trace tool produces but never writes
it to the screen — it only counts how many times a substring appears with
`strings.Count` and returns that count. The trace tool's own output carries
goroutine numbers, timestamps, and full file paths; none of these is even
assigned to a variable anywhere in the program, they are consumed and
discarded directly inside the counting operation.

`recordTrace` carries a similar discipline: the trace file's path sits in a
temporary directory set up with `os.MkdirTemp`, and this path is only used to
be passed to `eventCount` — it never enters any `fmt.Println` call. The
function signature is deliberate too: by taking `f func(ctx context.Context)`,
it leaves the work to be traced to the caller. This is why the same
`recordTrace` function can be used to trace both the sequential and the pooled
setup — only which function is called changes, the tracing mechanics stay the
same.

#### Why Three Workers

The pooled setup opens three workers and processes six records — the worker
count deliberately does not divide the record count evenly: each worker is
forced to process two records on average, but the order of reading from the
channel is not known in advance, so which worker gets how many records can
vary from run to run. This lesson never measures that distribution — it only
counts that three workers **started**, not how many records each of them got.
This is a deliberate limit so as not to mix with an axis the course
established in M09/K03 and does not use here (how a goroutine stops): this
lesson measures only how many execution units **exist**, not the sharing among
them.

#### Two Setups: Sequential And Pooled

The measurement below processes the same six records two separate ways. The
sequential setup processes every record in the calling goroutine itself, in
order. The pooled setup distributes the same six records across three worker
goroutines; each worker drops a tagged record once when it starts, then
processes records coming from the channel. In both setups, every record is
marked with the same tag through `trace.WithRegion`.

```go
// measurement.go — same work, two setups: sequential and a pool of three workers
package main

import (
	"context"
	"fmt"
	"os"
	"runtime/trace"
	"strconv"
	"sync"
)

type Record struct {
	Name   string
	Amount int
}

var records = []Record{
	{"bolt", 1}, {"nut", 2}, {"washer", 3}, {"nail", 4}, {"bolt", 5}, {"spacer", 6},
}

func process(ctx context.Context, k Record) int {
	result := 0
	trace.WithRegion(ctx, recordTag, func() {
		result = k.Amount * 2
	})
	return result
}

func sequential(ctx context.Context) int {
	total := 0
	for _, k := range records {
		total += process(ctx, k)
	}
	return total
}

func pooled(ctx context.Context, workerCount int) int {
	work := make(chan Record)
	var mu sync.Mutex
	total := 0
	var wg sync.WaitGroup
	for id := 0; id < workerCount; id++ {
		wg.Add(1)
		go func(id int) {
			defer wg.Done()
			trace.Log(ctx, workerStartTag, strconv.Itoa(id))
			for k := range work {
				v := process(ctx, k)
				mu.Lock()
				total += v
				mu.Unlock()
			}
		}(id)
	}
	for _, k := range records {
		work <- k
	}
	close(work)
	wg.Wait()
	return total
}

func main() {
	seqPath := recordTrace(func(ctx context.Context) { sequential(ctx) })
	defer os.RemoveAll(seqPath)
	poolPath := recordTrace(func(ctx context.Context) { pooled(ctx, 3) })
	defer os.RemoveAll(poolPath)

	fmt.Println("-- event count produced by the two setups --")
	fmt.Printf("%-10s %-25s %4d\n", "sequential", "record processing event", eventCount(seqPath, recordTag))
	fmt.Printf("%-10s %-25s %4d\n", "sequential", "worker start event", eventCount(seqPath, workerStartTag))
	fmt.Printf("%-10s %-25s %4d\n", "pooled", "record processing event", eventCount(poolPath, recordTag))
	fmt.Printf("%-10s %-25s %4d\n", "pooled", "worker start event", eventCount(poolPath, workerStartTag))
}
```

```
-- event count produced by the two setups --
sequential record processing event     12
sequential worker start event           0
pooled     record processing event     12
pooled     worker start event           3
```

**PM8.** `process` is called with the same function in both setups, and every
call is wrapped in a `trace.WithRegion` block — this causes the trace to
produce one start and one end event marked with `recordTag`. When six records
are processed, this corresponds to twelve events (six starts, six ends) —
regardless of the setup, because both setups process a total of six records.

In the `pooled` function, each worker makes the `trace.Log` call **before**
starting to read from the channel — not inside the loop, right before the
loop, once. This ordering is deliberate: if the tag were dropped again for
every record, the worker start count would also depend on the number of
records processed, and two separate pieces of information (how many workers
there are, how many records were processed) would get mixed together.
Dropping the tag only once, when a worker comes alive, keeps these two counts
**independent of each other**.

#### Reading the Numbers

The table shows two different behaviors. `record processing event` is
**twelve** in both setups — sequential or pooled, since the number of records
processed is the same, the event count produced is the same too. This row
shows **what the trace does not change**: the distribution of work itself does
not change the total amount of work done.

`worker start event`, however, is **zero** in the sequential setup and
**three** in the pooled setup. The sequential setup opens no extra goroutine
at all, so it produces no worker start event; the pooled setup opens three
workers, and each drops this tag once when it starts. This row explains **what
the trace shows**: how many separate execution units actually ran — a human
could also count that the `for id := 0; id < workerCount; id++` loop in the
source runs three times, but the trace reads this directly from the
**running** program, with no need to examine the source separately.

Reading these two rows together gives information neither could give alone.
If only `record processing event` were read, it would be seen that the two
setups do the same work, but not how. If only `worker start event` were read,
it would be seen that the pooled setup opens three workers, but not whether
these three workers actually did any work — all three could have stayed idle
and processed no record at all. Together, the two answer a third question:
three workers opened **and** all six records were genuinely processed, none
was lost, none was processed twice. This is proof that the channel-reading
mechanism works correctly — by a different path from the "passed" decision a
test gives, but the same kind of assurance.

The course's opening split eleven facts into source, tool, and run; this
lesson uses only the run out of those three classes — both the tagged regions
and the worker starts only emerge once the program is actually run and
traced.

#### Limiting Measurement: Collecting a Trace Changes Behavior

This lesson meets the course's duration ban again from another angle. The
`trace.Start` call begins recording every goroutine transition, every lock the
program makes — this recording itself is not free; extra work rides on top of
the program's normal flow as every event is processed and written. This is why
the behavior seen while tracing a program is **always somewhat different**
from its untraced form: collecting a trace changes the program's behavior.

This claim is not measured here — measuring it would require looking at
duration, and duration is not written in any lesson of this course. Instead it
is written down and justified with a sentence, just as the comparative
measurement lesson justified the duration ban: collecting a trace is itself
work, and that work is not zero. This is why the table above does not show how
many events an untraced program produces, but how many events a **traced**
program produces — the numbers must be read within this frame, without
assuming the same behavior would continue with tracing turned off.

This limit belongs to the same family as the previous three lessons' limits. A
profile only saw the path that ran; escape analysis only showed this
compiler's decision; the allocation count taught that a number going down is
not automatically an improvement. This lesson continues the same pattern: a
measurement tool presents what it measures together **with the act of
measuring itself**, and if the reader forgets this togetherness, they fall
into a false sense of certainty. A trace changing behavior by collecting it is
not a flaw of the trace tool — it is the unavoidable result of any observation
tool becoming part of the system it observes.

#### The Scope of This Measurement

What a real production trace reads is far broader than the two tags in this
lesson: when a scheduler stops one goroutine and runs another, how long a
goroutine waits on a lock, when a garbage collection cycle starts — all of
this sits inside the same trace and can be examined as a timeline with a
trace viewer. This kind of examination usually looks for an answer to a
specific problem: why did this request take longer than expected, which
goroutine blocked which resource. These questions involve duration, which is
why they are never asked in this course.

The method built in this lesson instead answers a narrow question: how **many
times** did a specific event happen. This is a small slice of the information
a real trace offers — it never sees the ordering in the timeline, the
overlap, the wait duration. But even this narrow question cannot be answered
by reading the source: someone reading the `pooled` function can see that
three goroutines were opened, but they can only learn whether these three
actually ran, whether they actually took work, by running and tracing it.

#### Summary

- An execution trace is a run fact, and in this lesson it was read as an
  event count; no goroutine number, timestamp, or stack trace was printed
  anywhere.
- Sequential and pooled setups processing the same six records produced the
  same number (twelve) of record processing events — the distribution of
  work did not change the total amount of work.
- The two setups produced a different number (zero, three) of worker start
  events — this difference comes from the trace directly showing how many
  separate execution units actually ran.
- The text the trace tool produced was never printed; only how many times
  the two chosen tags appeared was counted.
- Collecting a trace changes the program's behavior; this lesson did not
  measure this, it wrote it down in a sentence and presented the numbers
  together with this limit.
- An execution trace's raw content changes from run to run; this lesson
  therefore measured not the whole trace, but the part it tagged itself that
  came out stable.

#### Next Step

All four lessons of this topic learned something by **running** a program —
profile, escape decision, allocation count, event count; all of them could
only be read from a running program, and each had to admit that its own act
of measuring changed the program a little. The next topic steps out of this
ground entirely: a tool states which file enters the build, which platform is
targeted, and what gets embedded into the binary without ever running the
program — the run itself is no longer needed.

## Building and Distribution

Which of a directory's files enters the build determined by a build tag, a single command producing output for another platform, a version string embedded into the binary through a linker option, the compatibility promise a module publish carries, what a static analyzer finds and does not find in a known-defect scan, and the cost of calling a native library.

### Build Tags

Source: https://academia.sh/en/courses/go-production/build-tags

Which of six source files in the same directory enters the build is learned not from a running program but from asking the tool; the file count across four tag scenarios is read this way. The limiting measurement shows, by running it, that a file left out by its tag is not deleted from disk but that no tool ever examines it — a flaw in the file becomes visible only once the tag is activated.

The Performance and Memory topic's final lesson measured that a permission, the moment it is
granted, changes the program's behavior; the numbers were read under that effect from then on.
This lesson looks at only one of the same three classes (source, tool, run) and disables the
run entirely: before a program is ever run, even before it is ever compiled, which file in the
source tree enters that build is already decided. The one who makes this decision is neither a
person reading the code nor the running program itself — only the tool, reading a directive
above the source and filtering the file list. The next four lessons follow the same pattern:
which platform to target, what gets embedded in the binary, which version counts as compatible,
and which pattern counts as flawed — all of it is read from the tool alone, without waiting for
a run.

#### What a Build Tag Is

A **build tag** is a special comment line written at the very top of a source file, before the
package declaration: `//go:build`. Three formatting rules govern whether the directive works.
The comment must sit among the file's topmost lines; at least one blank line must separate it
from the package declaration; and no line of code may come between the directive and the rest
of the file. If any of these three rules is broken, the compiler treats the directive as an
ordinary comment and the file always enters the build — the directive's violation does not
produce an error, only silent neglect.

The directive's body is a small logical expression: a single name can stand alone
(`expansion`), two names can be joined with `&&` to require both active at once, `||` can
require at least one of the two active, or a leading negation mark can require a name to
**not** be active. These three operators can be combined with parentheses to any depth, but the
six files this lesson measures use each of the three only once — enough to see all three
corners without complicating the logic itself.

This logic's practical counterpart is usually one of two kinds of distinction. First, a package
carrying an extra file that is not normally compiled, only activated during a test or a
diagnostic session — it sits in the source tree without growing or slowing the everyday build.
Second, the same package carrying two mutually exclusive implementations — one default, the
other a variant that only takes effect when a given tag is supplied. What both share is that
the choice can be made **at build time**, without ever touching the source itself; the same
repository, the same branch, the same commit can turn into a different program depending only
on the `-tags` flag.

The tag names come from outside the program: they are defined nowhere within the source itself,
only supplied to the build command through the `-tags` flag. This marks a basic difference from
the build constants of the previous topic — a value written with `const` inside the source
stays the same across every build, while a build tag is determined **outside** the source,
separately on every invocation. The same source tree, with not a single line changed, can be
built from a completely different set of files depending on the tag set supplied.

#### Four Scenarios, One Logic

The measurement below sets up a small six-file package — one untagged, five each marked with a
different tag expression — and reads the number of files entering the build across four
different tag sets. The package is not written to a real directory but to a temporary module
opened with `os.MkdirTemp`; the path is never printed anywhere in the rest of this lesson.

```go
// helpers.go — the helpers this lesson shares: setting up a temporary package and calling the tool
package main

import (
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"
)

// show prints a fact, who states it, and its evidence on a single line.
func show(info, speaker, evidence string) {
	fmt.Printf("%-38s %-7s %s\n", info, speaker, evidence)
}

// setupModule writes the given files into a new temporary module; the path is never printed.
func setupModule(files map[string]string) string {
	dir, err := os.MkdirTemp("", "tag")
	if err != nil {
		return ""
	}
	os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module tagtrial\n\ngo 1.24\n"), 0o644)
	for name, content := range files {
		os.WriteFile(filepath.Join(dir, name), []byte(content), 0o644)
	}
	return dir
}

// runTool runs the language's own command, returning whether the exit succeeded; output is never printed.
func runTool(dir string, arg ...string) (string, bool) {
	cmd := exec.Command("go", arg...)
	cmd.Dir = dir
	cmd.Env = append(os.Environ(), "GOTOOLCHAIN=local")
	output, err := cmd.CombinedOutput()
	return string(output), err == nil
}

// fileCount counts, without calling any tool, the total number of .go files in the directory.
func fileCount(dir string) int {
	entries, _ := os.ReadDir(dir)
	n := 0
	for _, e := range entries {
		if strings.HasSuffix(e.Name(), ".go") {
			n++
		}
	}
	return n
}

// goFileCount reads from the tool the number of files entering the build with the given tag set.
func goFileCount(dir, tags string) int {
	arg := []string{"list", "-f", "{{len .GoFiles}}", "."}
	if tags != "" {
		arg = []string{"list", "-tags", tags, "-f", "{{len .GoFiles}}", "."}
	}
	output, _ := runTool(dir, arg...)
	n, _ := strconv.Atoi(strings.TrimSpace(output))
	return n
}

// fieldCount reads the length of any listing field ("go list" output such as GoFiles,
// TestGoFiles); which field to ask for is given by the template parameter.
func fieldCount(dir, tmpl string) int {
	output, _ := runTool(dir, "list", "-f", tmpl, ".")
	n, _ := strconv.Atoi(strings.TrimSpace(output))
	return n
}
```

```go
// measurement.go — which files enter the build, read through the tool across four tag scenarios
package main

import "fmt"

func main() {
	files := map[string]string{
		"core.go":      "package main\n\nfunc coreFunction() int { return 1 }\n",
		"expansion.go": "//go:build expansion\n\npackage main\n\nfunc expansionFunction() int { return 2 }\n",
		"trial.go":     "//go:build trial\n\npackage main\n\nimport \"fmt\"\n\nfunc trialFunction() int {\n\tfmt.Printf(\"%d\\n\", \"error\")\n\treturn 3\n}\n",
		"both.go":      "//go:build expansion && trial\n\npackage main\n\nfunc bothFunction() int { return 4 }\n",
		"either.go":    "//go:build expansion || trial\n\npackage main\n\nfunc eitherFunction() int { return 5 }\n",
		"not_trial.go": "//go:build !trial\n\npackage main\n\nfunc notTrialFunction() int { return 6 }\n",
	}
	dir := setupModule(files)
	defer func() { _ = dir }()

	fmt.Println("-- which files enter the build: four tag scenarios --")
	show("unlabeled file count", "tool", fmt.Sprint(goFileCount(dir, "")))
	show("file count with expansion tag", "tool", fmt.Sprint(goFileCount(dir, "expansion")))
	show("file count with trial tag", "tool", fmt.Sprint(goFileCount(dir, "trial")))
	show("file count with both tags at once", "tool", fmt.Sprint(goFileCount(dir, "expansion,trial")))

	fmt.Println()
	fmt.Println("-- limiting measurement: a left-out file is not deleted, not tested --")
	show(".go file count on disk", "tool", fmt.Sprint(fileCount(dir)))
	show("count entering unlabeled build", "tool", fmt.Sprint(goFileCount(dir, "")))

	_, unlabeledClean := runTool(dir, "vet", "./...")
	_, trialClean := runTool(dir, "vet", "-tags", "trial", "./...")
	show("unlabeled analysis result", "tool", fmt.Sprint("clean=", unlabeledClean))
	show("analysis result with trial tag", "tool", fmt.Sprint("clean=", trialClean))
}
```

```
-- which files enter the build: four tag scenarios --
unlabeled file count                   tool    2
file count with expansion tag          tool    4
file count with trial tag              tool    3
file count with both tags at once      tool    5

-- limiting measurement: a left-out file is not deleted, not tested --
.go file count on disk                 tool    6
count entering unlabeled build         tool    2
unlabeled analysis result              tool    clean=true
analysis result with trial tag         tool    clean=false
```

**BD1.** None of these ten lines run a program; the bodies of functions like `trialFunction`
and `bothFunction` were never executed. The only thing measured is the file list itself — which
is why the whole table is written under the **tool** class, and the course's `run` column stays
empty for the entire lesson.

The six files' tags sample all three operators: `core.go` carries no tag at all, `expansion.go`
and `trial.go` carry a single name each, `both.go` carries an `&&`, `either.go` an `||`, and
`not_trial.go` a `trial` with a leading negation mark. In the unlabeled call, only the untagged
file and the two files satisfying a negated condition remain — since `trial` is not active,
`not_trial.go` is included too. When called with the `expansion` tag, both its own tagged file
and `either.go`, which satisfies the `||` condition, are added to the table, but `both.go` still
stays out because the `&&` condition needs a second tag it does not have. **BD2.** Only when
both tags are supplied at once does `both.go` enter the table, and at the same moment
`not_trial.go` drops out — because `trial` is now active and the negated condition breaks. The
four numbers (2, 4, 3, 5) are therefore the result of four separate logical evaluations, not
four separate runs.

#### Naming Is Also an Implicit Tag: `_test.go`

A build tag is not the only mechanism. The `_test.go` suffix appended to a file name answers
the same question without a single `//go:build` directive written: does this file enter the
normal build? The answer is no — but that does not mean the file is never compiled at all; it
is only compiled in a separate list, under `TestGoFiles`. The measurement below sets up a
two-file package — one untagged, one carrying the `_test.go` suffix — and reads two separate
fields.

```go
// naming.go — the _test.go suffix removes a file from the normal build with no directive written
package main

import "fmt"

func main() {
	files := map[string]string{
		"core.go":      "package main\n\nfunc coreFunction() int { return 1 }\n",
		"core_test.go": "package main\n\nimport \"testing\"\n\nfunc TestCoreFunction(t *testing.T) {\n\tif coreFunction() != 1 {\n\t\tt.Fatal(\"unexpected\")\n\t}\n}\n",
	}
	dir := setupModule(files)
	defer func() { _ = dir }()

	fmt.Println("-- naming is also an implicit tag: _test.go --")
	show(".go file count on disk", "tool", fmt.Sprint(fileCount(dir)))
	show("file count entering normal build", "tool", fmt.Sprint(fieldCount(dir, "{{len .GoFiles}}")))
	show("file count compiled for test", "tool", fmt.Sprint(fieldCount(dir, "{{len .TestGoFiles}}")))
}
```

```
-- naming is also an implicit tag: _test.go --
.go file count on disk                 tool    2
file count entering normal build       tool    1
file count compiled for test           tool    1
```

**BD3.** Two files sit on disk, but neither mixes into the other's list: the normal build sees
only `core.go`, the test build only `core_test.go`. This split rests not on a directive but on a
plain name — seeing `_test.go` at the end of a file name is a rule built into the tool itself,
never written anywhere in the source. The `//go:build` directive adds an optional second layer
on top of this rule; the naming convention is always there, working automatically for every
package. Both mechanisms answer the same question — which file enters which list — but one
rests on a directive explicitly written in the source, the other only on a name.

#### Limiting Measurement: A File Is Not Deleted, Not Tested

The second half of the table above asks a completely different question: what happens to a file
left out by its tag? The answer has two parts, and both are measured. First: the number of
`.go` files in the directory is always six — no tag combination deletes a file from disk, only
removes it from that run's build list. The `fileCount` function counts this directly with
`os.ReadDir`, without calling any `go` command at all; **BD4.** against six files on disk, only
two enter the unlabeled build — the other four are neither lost nor broken, only sitting outside
that run's field of view.

Second, and sharper: `trial.go` had a flaw deliberately written into it — a string is given to
a `%d` formatter, and this kind of mismatch is caught not by the compiler but by the analyzer
introduced in the Go Command Family lesson. Run without a tag, this tool says `clean=true`,
because the flawed file is not in the build list at all — the tool does not even read it.
**BD5.** The same tool, called with the `trial` tag, flips to `clean=false`; the flaw did not
change, only the file's visibility changed. This is the full proof of the claim "a file left out
by its tag is not tested": the flaw waits in a corner no tool can reach, until someone activates
the tag and runs it.

This boundary works both ways in practice. On one hand, when experimental or platform-specific
code is written behind a tag, that code's quality sits in an area the main build and the main
analysis never check — the tag only decides in which environment it gets built, it does not
guarantee that code's correctness. On the other hand, this is exactly the reason tags exist: a
source tree can carry multiple variants that are never meant to run at the same time — a variant
outside the main flow can sit on disk without ever affecting the main flow. The tag's decision is
made before the build, with a single text comparison; this is why it is both very cheap and very
precise, but its consequences are visible only to whoever supplies the tag.

The balance between these two sides turns into a shared responsibility. Whoever writes a file
behind a tag knows that the file has vanished from the normal flow's view; regularly compiling
and analyzing it together with its tag therefore becomes the **only** way to keep that file's
quality up. The tag itself issues no reminder at all — the flaw inside `trial.go` can go unnoticed
for weeks, even as the source tree grows, unless someone explicitly supplies `-tags trial`. This
lesson shows this risk through a concrete number: in a small six-file example the difference was
a single line, but in a real repository, as the number of tagged files grows, so does the amount
of source the untagged build and the untagged analysis **do not see** — and this amount itself
can only be measured the same way, by asking the tool alone and comparing file lists.

#### Summary

- A build tag is a directive that determines, from outside the source, through the `-tags` flag,
  whether a file enters the build; the decision is given by the **tool** without running any
  program.
- Tag expressions are built with `&&`, `||` and a negation mark; the same six-file source can
  produce a file list ranging from two to five depending on the tag set supplied.
- A file left out by its tag is not deleted from disk — it only stays outside that run's build
  list; the file count in the directory always stays fixed.
- A flaw in a left-out file is invisible because no tool reads that file; the same tool finds
  the same flaw immediately once the tag is activated.
- This lesson never used the `run` class — every result was obtained without calling a single
  function, only by reading the file list and the analysis result.

#### Next Step

The question measured in this lesson was limited to "which file" — once the files were settled,
all of them compiled to a single target, the machine actually running the build. The next lesson
asks one step further along the same question: can the same source, with nothing changed, be
built for a target completely different from the machine? The answer again comes without waiting
for a run, read from a single command's pass-or-fail result — but this time with a limit attached:
being compiled does not mean it will run on the target.

### Cross-Compilation

Source: https://academia.sh/en/courses/go-production/cross-compilation

It is measured whether the same portable source compiles for three separate targets with a single command, with none of those targets installed; compilability here too is a tool fact that requires no run. A second measurement shows that a platform-dependent call's compilability changes across the same three targets. The limiting measurement shows, by modeling, that being compiled does not mean it will run on the target — that an assumption carrying the same name can hold for one target family and fail for another.

The previous lesson showed that which file in a directory enters the build can be read from the
tool alone, without running any program. This lesson carries the same question one step further:
once the files are settled, which target do they compile for? The answer again comes without
waiting for a run, but this time it carries its own limit along with it — being compiled does
not mean it will run on that target.

#### One Command, Three Targets

**Cross-compilation** is the process of producing a binary for a target other than the machine
the source is built on. The language's own compiler does this job with a single command; what
selects the target is two environment variables — one sets the target operating system family,
the other the processor architecture. Both variables come from **outside** the build command,
just like the build tags of the previous lesson; the difference is that a tag's name is specific
to the source, while these two variables' names and possible values are fixed by the language
itself. No third, target machine is needed: the compiler, independent of the machine it runs on,
produces a binary for the requested target, and whether it can produce one is again information
that falls into the **tool** class — learned without running anything.

This draws on the same underlying mechanism as the previous lesson. The standard library itself
keeps many of its functions in a separate file per target family, behind a build tag carrying
that family's name (or an equivalent file-naming convention); the compiler reads which target it
is working for from the two environment variables and picks the right file set. The difference
is scale: a selection set up by hand in a six-file example in the previous lesson runs, in the
standard library itself, across thousands of files, separately for each target. This is why a
target does not need to be "installed" — all that is needed is for the compiler to know which
source files are valid for that target, and this knowledge already sits in the source itself, in
the file names and the tags.

The measurement below compiles a portable source containing no platform-specific call at all for
three separate targets and reads whether each attempt passes. The produced binary is never
written anywhere — the output is redirected to the system's null device, because the only thing
measured in this lesson is whether the build succeeds, not the produced file itself.

```go
// helpers.go — the helpers this lesson shares: setting up a temporary package and calling the tool
package main

import (
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
)

// show prints a fact, who states it, and its evidence on a single line.
func show(info, speaker, evidence string) {
	fmt.Printf("%-42s %-7s %s\n", info, speaker, evidence)
}

// setupModule writes the given files into a new temporary module; the path is never printed.
func setupModule(files map[string]string) string {
	dir, err := os.MkdirTemp("", "cross")
	if err != nil {
		return ""
	}
	os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module cross\n\ngo 1.24\n"), 0o644)
	for name, content := range files {
		os.WriteFile(filepath.Join(dir, name), []byte(content), 0o644)
	}
	return dir
}

// compiles attempts to build the source for the given target; the output is never printed,
// only whether it succeeded is returned. The produced binary is written to the null device.
func compiles(dir string, extraEnv []string) bool {
	cmd := exec.Command("go", "build", "-o", os.DevNull, ".")
	cmd.Dir = dir
	cmd.Env = append(append(os.Environ(), "GOTOOLCHAIN=local", "CGO_ENABLED=0"), extraEnv...)
	return cmd.Run() == nil
}
```

```go
// threeTargets.go — a portable source compiles for three separate targets, with no target machine needed
package main

import "fmt"

func main() {
	dir := setupModule(map[string]string{
		"portable.go": "package main\n\nfunc add(a, b int) int { return a + b }\n\nfunc main() {\n\t_ = add(1, 2)\n}\n",
	})
	defer func() { _ = dir }()

	targets := []struct{ name, goos, goarch string }{
		{"first target", "linux", "amd64"},
		{"second target", "windows", "amd64"},
		{"third target", "darwin", "arm64"},
	}

	fmt.Println("-- one command, three targets: portable source --")
	for _, t := range targets {
		env := []string{"GOOS=" + t.goos, "GOARCH=" + t.goarch}
		show(t.name+" compiled", "tool", fmt.Sprint(compiles(dir, env)))
	}
}
```

```
-- one command, three targets: portable source --
first target compiled                      tool    true
second target compiled                     tool    true
third target compiled                      tool    true
```

**BD6.** All three separate targets compiled, and this machine held no real copy of any of the
three — the only thing run was the compiler itself, called three times with two environment
variables. The result again leaves the `run` column entirely empty: which targets a portable
source compiles for, without touching any of those targets, is read from a single command's
pass-or-fail answer.

#### Same Call, Changing Result

The source above contained nothing platform-specific, which is why all three targets gave the
same answer. This uniformity breaks once a call binds to a platform-specific system function —
some operating system families define that function, some do not define it at all. The
measurement below tries, on the same three targets, a source that uses a single system call
from the standard library specific to one operating system family (the named-pipe creation
call).

```go
// systemCall.go — same call, three targets: a platform-dependent system call
package main

import "fmt"

func main() {
	dir := setupModule(map[string]string{
		"caller.go": "package main\n\nimport \"syscall\"\n\nfunc main() {\n\t_ = syscall.Mkfifo\n}\n",
	})
	defer func() { _ = dir }()

	targets := []struct{ name, goos, goarch string }{
		{"first target", "linux", "amd64"},
		{"second target", "windows", "amd64"},
		{"third target", "darwin", "arm64"},
	}

	fmt.Println("-- same call, three targets: a platform-dependent system call --")
	succeeded := 0
	for _, t := range targets {
		env := []string{"GOOS=" + t.goos, "GOARCH=" + t.goarch}
		c := compiles(dir, env)
		if c {
			succeeded++
		}
		show(t.name+" compiled", "tool", fmt.Sprint(c))
	}
	show("how many of three targets compiled", "tool", fmt.Sprint(succeeded))
}
```

```
-- same call, three targets: a platform-dependent system call --
first target compiled                      tool    true
second target compiled                     tool    false
third target compiled                      tool    true
how many of three targets compiled         tool    2
```

**BD7.** Same source, same three targets, but this time the result is not uniform: two targets
compile, one does not. The standard library's system call package carries a separate
implementation for each operating system family, and calling a function that does not exist at
all in one family stops the build the moment that family is targeted — and this sits one level
deeper than the "which file enters the build" question measured in the previous lesson, not in
the same layer: the file itself enters the build list for all three targets, but a name inside
that file is defined in two of the three targets and undefined in one. Which family defines
which call is, again, **tool** knowledge: a person might know this by heart, but the build
itself verifies it the same way every time, requiring no run.

The practical consequence of this difference is that a source that must support several targets
at once needs to be written one of two ways. Either the platform-specific call is never used at
all — like the portable source in the previous section, giving the same behavior on all three
targets — or the platform-specific part is split into its own file, behind its own tag, and a
separate implementation is written for each target. The second path carries the same shape as
the previous lesson's `expansion`/`trial` example: a file name or a tag decides which
implementation takes effect on which target. The difference is that this time the choice is made
automatically by the build command's own target information, not by a manually supplied flag. A
single file compiling on two of three targets and failing on the third is therefore not a flaw —
it is a sign that the source supplies no alternative file at all for the targets that do not
support that call.

#### Limiting Measurement: Compiled Is Not the Same as Working

Both measurements above stayed within the same boundary: whether a source compiles for a given
target. This is a separate question from whether that source **runs** on that target, and this
lesson genuinely cannot answer that second question — two of the three targets have no real
copy on this machine, the produced binary cannot be run. Rather than hide this limit, we model
it openly: the measurement below is never run on a real target at all, two target families'
environments are set up by hand inside the source.

```go
// assumptionModel.go — limiting measurement: compiled is not the same as working, shown through a model
package main

import "fmt"

// findHomePath looks up the home directory in the given environment map by a SINGLE variable name.
func findHomePath(env map[string]string, variableName string) (string, bool) {
	value, found := env[variableName]
	return value, found
}

func main() {
	// two target families' environments are modeled here by hand, without ever really running:
	// each family offers a variable carrying the user's home directory under its own name.
	firstFamilyEnv := map[string]string{"HOME": "user-directory"}
	secondFamilyEnv := map[string]string{"USERPROFILE": "user-directory"}

	// despite the portable-looking name, the code searches for only ONE variable name.
	searchedVariable := "HOME"

	_, firstFound := findHomePath(firstFamilyEnv, searchedVariable)
	_, secondFound := findHomePath(secondFamilyEnv, searchedVariable)

	fmt.Println("-- limiting measurement: compiled is not the same as working (model) --")
	show("home path found in first target family", "tool", fmt.Sprint(firstFound))
	show("home path found in second target family", "tool", fmt.Sprint(secondFound))
}
```

```
-- limiting measurement: compiled is not the same as working (model) --
home path found in first target family     tool    true
home path found in second target family    tool    false
```

**BD8.** The `findHomePath` function contains no platform-specific call at all — it looks up a
map by a single string key — which is why, if it had been compiled for three real targets like
above, it would compile without trouble on all three. The flaw is not in the build, it is in the
**assumption**: the code looks up only a single variable name, and this name is valid in the
first target family, invalid in the second. In a real program this line might be a call trying
to find a user's settings directory; run on the second target family, it could silently return
empty, with no build error and no runtime crash. This is why the "compiled=true" line is
reliable information here too, just as in the previous two measurements — but it says what is
**true**, not what **works well**. This distinction cannot be measured without modeling it,
because a run itself never genuinely happens on a real target in this lesson.

The model itself was kept deliberately small: two maps, one string key, two lookups. In a real
program this kind of assumption is usually buried much deeper — the directory a configuration
file is searched in, the place a log file is written to, the name of a temporary file — and all
of them share the same pattern: the code accepts a single name, a single path, or a single
format as correct, this acceptance passes the build on all three targets, and it surfaces only
on the target where it is wrong, only once that target is actually run. This is exactly where the
power of the compilability measurement ends: what it says is true and reliable, but the question
it asks is narrow — it only answers "does this source turn into a binary for this target," not
"does this binary do the right thing."

The way this limit is closed in practice stays within the same three classes: the only reliable
way to show that something truly works on a target is to genuinely **run** it there — on a real
machine or in an environment simulating that target. The compilability test does not replace
this step, it precedes it: if a source does not even compile for one of three targets, there is
no need to try a run on that target at all. For a source that compiles on all three targets, the
question is still open — the compilability test removes this open question **early**, at a
cheap step, but it does not close it. In this lesson the second step (a real run) was never
taken, because two of the three targets have no real copy on this machine; this is a limit of
this measurement's scope, not a limit of cross-compilation itself.

The same limit applies to one of cross-compilation's own boundaries: when a source links to a
native library through `cgo`, the compiler usually cannot find the external compiler it needs
for the target, and the build is genuinely rejected this time — that is the subject of the
course's final lesson. The three targets measured in this lesson were all pure source; no
external compiler was ever needed, and so none of them was ever rejected.

#### Summary

- Cross-compilation is the process of producing a binary for a target with no target system
  environment installed at all, with a single command; compilability here too is a **tool** fact
  that requires no run.
- A portable source gave the same answer on all three targets; once a single platform-dependent
  system call was added, only two of the same three targets compiled.
- Which target family a call is defined in is also information read from the tool — the build
  verifies this the same way on every attempt, requiring no personal knowledge.
- Limiting measurement: being compiled does not mean it will work correctly on the target; an
  assumption tied to the same name can hold for one target family and fail for another.
- This limit was shown not by running on a real target but by openly modeling two targets'
  environments inside the source; nowhere in this lesson was a real target machine ever touched.

#### Next Step

In this lesson the target was a choice under which the file itself never changed — the source
was fixed, only the environment it compiled for changed. The next lesson takes up a value that
is never written into the source at all, but still enters the binary: a version fact is placed
into the binary not on any line of the source, but only at build time, by a separate tool. The
question asked this time is not "which target" but "where does a value never written in the
source come from" — and just as the target chosen in this lesson leaves no trace in the binary,
that value leaves no trace in the source itself either; both are added to the binary only by the
build process's own tools, from outside the source.

### Linker Options

Source: https://academia.sh/en/courses/go-production/linker-options

It is measured that a string value written nowhere in the source gets embedded into the binary in three separate forms across three separate build calls; the source text stays character-for-character identical across all three. The limiting measurement shows, by running it, that the embedded value is never validated by the linker — that an unformatted string is accepted just as silently — and that a separate linker option removing the symbol table does not change the program's output.

In the previous lesson, the chosen target left no trace in the binary, determined only by the
build process's own tools. This lesson carries the same pattern forward, this time for a single
string value instead of a directory or an architecture: a value written nowhere in the source
enters the binary again at build time, again from outside the source. This time the party asking
the question is not the compiler but the **linker** — the tool that runs in the final step of
compiling the source, joining separately compiled parts into a single binary.

#### A Value Not in the Source

A package-level variable can be declared in the source with a default value, and this value can
be changed at build time with a **linker flag**. The measurement below runs a single source — a
single `version` variable and a `main` function printing it — through three separate build
calls. The source text stays character-for-character identical across all three; the only thing
that changes is a flag supplied to the command.

The flag itself carries an equality expression: the variable's **full package path**, a dot, its
name, then the requested value. As the linker joins separately compiled package parts into a
single binary, it already keeps a table of every name each package exports — this table normally
exists to bind calls between packages together. The `-X` flag uses this same table for another
purpose: it looks up the name in the table and puts the given string in place of the value the
source declared first. This is why the target name must be written **fully** and **correctly** —
if the package path is wrong or the name itself is misspelled, the linker usually finds no match
at all and silently ignores the flag; the default value from the source stays as it was.

```go
// helpers.go — the helpers this lesson shares: setting up a temporary package and calling the tool
package main

import (
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
)

// show prints a fact, who states it, and its evidence on a single line.
func show(info, speaker, evidence string) {
	fmt.Printf("%-38s %-7s %s\n", info, speaker, evidence)
}

// setupModule writes the given files into a new temporary module; the path is never printed.
func setupModule(files map[string]string) string {
	dir, err := os.MkdirTemp("", "linker")
	if err != nil {
		return ""
	}
	os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module linktrial\n\ngo 1.24\n"), 0o644)
	for name, content := range files {
		os.WriteFile(filepath.Join(dir, name), []byte(content), 0o644)
	}
	return dir
}

// runTool runs the language's own command, returning the output and whether it succeeded.
func runTool(dir string, arg ...string) (string, bool) {
	cmd := exec.Command("go", arg...)
	cmd.Dir = dir
	cmd.Env = append(os.Environ(), "GOTOOLCHAIN=local")
	output, err := cmd.CombinedOutput()
	return string(output), err == nil
}

// run runs the source with the given linker value and returns the single line it printed. If
// apply is false, no -X flag is supplied; if true, the value is applied even if it is empty.
func run(dir string, apply bool, value string) string {
	arg := []string{"run", "."}
	if apply {
		arg = []string{"run", "-ldflags=-X main.version=" + value, "."}
	}
	output, _ := runTool(dir, arg...)
	return strings.TrimRight(output, "\n")
}

// symbolPresent says whether a given symbol name is present in a binary built with the given
// extra linker flag; the binary itself and its path are never printed anywhere.
func symbolPresent(dir, extraFlag, symbolName string) bool {
	binaryPath := filepath.Join(dir, "binary")
	arg := []string{"build", "-o", binaryPath}
	if extraFlag != "" {
		arg = append(arg, "-ldflags="+extraFlag)
	}
	arg = append(arg, ".")
	if _, ok := runTool(dir, arg...); !ok {
		return false
	}
	output, _ := runTool(dir, "tool", "nm", binaryPath)
	return strings.Contains(output, symbolName)
}
```

```go
// measurement1.go — a value not written in the source, read three different ways from the same source
package main

import "fmt"

const sourceText = "package main\n\nimport \"fmt\"\n\nvar version = \"development\"\n\nfunc main() {\n\tfmt.Println(version)\n}\n"

func main() {
	dir := setupModule(map[string]string{"prog.go": sourceText})
	defer func() { _ = dir }()

	fmt.Println("-- same source, three different linker inputs --")
	show("no value supplied", "tool", run(dir, false, ""))
	show("first value supplied", "tool", run(dir, true, "version-a"))
	show("second value supplied", "tool", run(dir, true, "version-b"))
}
```

```
-- same source, three different linker inputs --
no value supplied                      tool    development
first value supplied                   tool    version-a
second value supplied                  tool    version-b
```

**BD9.** The `sourceText` constant is character-for-character identical across all three runs;
the only thing that changes is the flag given to the `run` function. The only value actually
written in the source is `"development"` — the value shown when no flag is supplied at all, the
one line belonging to the **source** class. The other two lines are nowhere in the source; the
linker replaces a package-level string variable's initial value with a string supplied from
outside, during the build, without ever touching the source. All three of these lines are written
to the **tool** class, because the value gives the same result on the same flag every time — there
is nothing changing from run to run, only which flag is supplied changes.

This mechanism can only work under two conditions at once. The variable must be at package level
— a local variable inside a function sits somewhere the linker cannot reach. And the variable's
type must be a string; the linker can only change string values this way, it cannot change an
integer or a struct's field through the same path. These two constraints keep the mechanism
deliberately narrow: it exists to place a single piece of text at build time, not a complex data
structure.

This narrowness is not a shortcoming, it is a design choice. The linker never parses the source
at all, it works only with the binary representation inside compiled object files — a string
variable's binary representation is a memory block whose length and bytes are known in advance,
so all the linker has to do is change that block's contents. The same operation would be far
more fragile for an integer or a struct: putting an arbitrary byte in place without knowing the
value's type and size would risk corrupting the rest of the binary. A string type carries no
such risk, because Go's string representation is already kept together with a length field, and
the linker can write the new value's length into that field too.

#### Limiting Measurement: The Embedded Value Is Not Validated

All three values above were formatted, readable strings. The linker itself never looks at this
form at all — it places whatever it is given exactly as it is. The measurement below tests this
with two extremes: on one side a string matching no recognizable pattern at all, on the other a
completely empty string.

```go
// measurement2.go — limiting measurement: the embedded value is not validated, the symbol table is a separate matter
package main

import "fmt"

const sourceText = "package main\n\nimport \"fmt\"\n\nvar version = \"development\"\n\nfunc main() {\n\tfmt.Println(version)\n}\n"

func main() {
	dir := setupModule(map[string]string{"prog.go": sourceText})
	defer func() { _ = dir }()

	fmt.Println("-- limiting measurement: the embedded value is not validated --")
	show("unformatted value supplied", "tool", run(dir, true, "###INVALID-VERSION-999"))
	show("empty string supplied", "tool", "["+run(dir, true, "")+"]")

	fmt.Println()
	fmt.Println("-- second linker option: removing the symbol table --")
	show("symbol present in normal build", "tool", fmt.Sprint(symbolPresent(dir, "", "main.version")))
	show("symbol present with -s build", "tool", fmt.Sprint(symbolPresent(dir, "-s -w", "main.version")))
	show("does -s change behavior", "tool", "output="+run(dir, false, ""))
}
```

```
-- limiting measurement: the embedded value is not validated --
unformatted value supplied             tool    ###INVALID-VERSION-999
empty string supplied                  tool    []

-- second linker option: removing the symbol table --
symbol present in normal build         tool    true
symbol present with -s build           tool    false
does -s change behavior                tool    output=development
```

**BD10.** The first line accepted a string that does not look like a version number at all, with
no warning, no rejection; the second line accepted a completely empty string the same way — the
brackets are empty because the variable itself was set equal to an empty string. The linker
performs no type check and no format check here; all it does is put the given byte sequence in
place of the source code's initial value. This is another face of the previous lesson's "compiled
is not the same as working" limit: if the linker passes, the embedded value is never guaranteed to
be **meaningful** — only that it was placed in the right spot, with the right type.

The only way to close this gap is to validate the embedded value outside the source, in a
separate step — the linker itself never takes on this job. In a deployment process this is
usually solved by testing, **before** the linker is ever called, whether the string to be
embedded matches a pattern known in advance to the caller — exactly as this lesson measured, the
validation comes not from the linker itself but from the discipline of the process calling it.
This lesson did not apply that discipline, deliberately: the goal was to see the linker's own
behavior in its bare form, with no surrounding validation layer at all.

**BD11.** The second measurement looks at a separate linker option: the flag that removes the
symbol table and debug information. In a normal build, the name `main.version` is present in the
binary's symbol table — this name lets a debugger or an analysis tool recognize the variable.
With the same flag added, this name is removed from the symbol table, but the program's own
output — the `run` call on the last line — does not change at all. This shows that two different
linker options work at two different layers: one changes **the program's data** (`-X`), the other
only removes **information about the program** (`-s`) — the two can be used together, and one
never affects the other's result.

This distinction is not arbitrary: the symbol table and debug information that `-s` removes are
**no part at all** of the program's own logic — extra data added to the binary only so a debugger
can read variable names, function names, and line numbers, but never used by the program itself
at run time. `-X`, by contrast, writes into exactly the opposite place — inside the program's own
runtime data. Even when both options are given in the same command, no ordering problem arises
between them, because the symbol table is removed **at the end of the build process**, once every
value has already settled into place — what gets removed is the names themselves, not the data
those names point to.

#### Without Leaving a Trace in the Source

What both measurements share is that the layer the linker works in is separate from the source
itself. The source file stayed character-for-character identical across the `version-a`,
`version-b`, and `###INVALID-VERSION-999` runs; none of them appears in any version of the source
on disk. This completes the parallel pointed to at the end of the previous lesson: just as target
selection enters the binary leaving no trace at all, an embedded string enters leaving no trace
in the source either. Both are applied by the build process's own tools, as part of the build's
**input** — not the source's **output**.

The practical consequence of this is that every binary produced from the same source tree can
carry its own "identity": values like a version string, a build timestamp, a distribution ID can
be given separately to each binary, without ever changing the source, only by changing the build
call. The cost sits in the same place: the correctness of these values falls outside source
review — without a watching, reviewing person or process, a wrong value can silently enter the
binary. This difference looks sharper when compared, in the next lesson, against a version number
**genuinely written** in the source: there, the version number sits in the source itself, and a
tool only **checks** it; here, the version-like value never sits in the source at all, and no
tool checks it.

The choice between these two extremes usually depends on one question: is the value part of the
source's **identity**, or is it part of **when, in what environment** the source was built? A
library's version number is an example of the first — that value moves with the source, it does
not change without the source changing, so writing it into the source keeps a more consistent
record. A build ID or a fact about the build environment is an example of the second — this value
does not come from the source, it comes from **that particular** build call, and it can be
expected to differ on every build; writing it into the source would, in this case, mean polluting
the source with information that does not actually belong to it. A linker option meets exactly
this second case, without ever touching the source.

#### Summary

- A string variable declared at package level can be filled with a different value at build time
  through a linker flag, with the source never changing; this value is never written in the
  source.
- The same source gave three different outputs across three separate calls; the difference was
  only in the flag supplied, never in any version of the source file.
- Limiting measurement: the linker never validates the embedded value — an unformatted string and
  an empty string are both accepted the same way, with no warning produced.
- A separate linker option removing the symbol table does not change the program's own output;
  the two options work in two independent layers.
- Target selection (previous lesson) and the embedded value (this lesson) share the same pattern:
  both are part of the build's input, not part of the source itself.

#### Next Step

In this lesson the embedded value was never written in the source, and no tool ever checked it.
The next lesson takes up the exact opposite case: a version number is declared openly, in the
source itself — but this time a tool checks a promise that number carries, whether it stays
compatible with old code. The question is no longer "where does the value come from" but "what
does a number written in the source guarantee."

### Module Publishing

Source: https://academia.sh/en/courses/go-production/module-publishing

A version number is declared openly in the source, but the compatibility promise that version carries is checked not by the source itself but by a tool: the signatures of exported functions are compared, counting the names added, changed, and removed. The static verdict is confirmed by genuinely compiling the same source. The limiting measurement shows, by running it, that the promise rests on signatures — that two implementations carrying the identical signature can give a completely different result, and the tool never sees this at all.

In the previous lesson the embedded value was never written in the source, and no tool ever
checked it. This lesson starts from the exact opposite kind of source: a version number is
declared openly, in the source itself. But this number written in the source guarantees nothing
by itself — what guarantees something is a promise that number carries, and a separate tool that
checks whether that promise held.

#### Version in the Source, Compatibility from the Tool

A module's **compatibility promise** between successive versions says that a new version will
not break code using the old version. This promise is declared in the source as a number, but the
number itself performs no check; there is no mechanism watching whether the correct number was
written. What can be checked is not the promise's **behavioral** counterpart — what happens
inside the implementation — but its **signature** counterpart: has an exported function's name,
parameters, and return type changed from one version to another?

The measurement below builds a small comparator using the standard library's own parsing
packages — not a real publishing tool, but one modeled to answer this lesson's question. It
parses two source texts, extracts the signature of every exported function in each, and compares
the two sets.

The comparator itself has three steps. First each source is turned, by the language's own
parser, into a syntax tree — this step performs no type resolution at all, it only reads the
source's syntax. Then every function declaration in the tree is filtered: declarations with no
method receiver (that is, ordinary functions) and a capitalized name are kept, everything else —
methods, unexported helper functions — is discarded. In the last step, each remaining function's
type (its parameter list and return list) is printed back out into source form, and this printed
text is kept in a dictionary as that function's **signature**. The dictionary's key is the
function name, its value the signature text; the two versions' dictionaries can therefore be
compared directly.

```go
// helpers.go — the helpers this lesson shares: extracting signatures, setting up a temporary package, calling the tool
package main

import (
	"bytes"
	"fmt"
	"go/ast"
	"go/parser"
	"go/printer"
	"go/token"
	"os"
	"os/exec"
	"path/filepath"
	"sort"
	"strings"
)

// show prints a fact, who states it, and its evidence on a single line.
func show(info, speaker, evidence string) {
	fmt.Printf("%-46s %-7s %s\n", info, speaker, evidence)
}

// signatures maps the name of every exported free function in the given source to its printed
// type signature. A small model built with the standard library's own parsing packages; not a
// real compatibility tool.
func signatures(source string) map[string]string {
	fset := token.NewFileSet()
	file, err := parser.ParseFile(fset, "source.go", source, 0)
	if err != nil {
		return nil
	}
	result := map[string]string{}
	for _, decl := range file.Decls {
		fd, ok := decl.(*ast.FuncDecl)
		if !ok || fd.Recv != nil {
			continue
		}
		name := fd.Name.Name
		if !ast.IsExported(name) {
			continue
		}
		var buf bytes.Buffer
		printer.Fprint(&buf, fset, fd.Type)
		result[name] = strings.TrimPrefix(buf.String(), "func")
	}
	return result
}

// compare returns the names added, changed, and removed between two signature sets.
func compare(previous, next map[string]string) (added, changed, removed []string) {
	for name, nextSig := range next {
		prevSig, existed := previous[name]
		if !existed {
			added = append(added, name)
		} else if prevSig != nextSig {
			changed = append(changed, name)
		}
	}
	for name := range previous {
		if _, exists := next[name]; !exists {
			removed = append(removed, name)
		}
	}
	sort.Strings(added)
	sort.Strings(changed)
	sort.Strings(removed)
	return
}

// compatible says, by looking only at signatures, whether code using the old version will also
// compile against the new version: no name may be removed and no signature may have changed.
func compatible(previous, next map[string]string) bool {
	_, changed, removed := compare(previous, next)
	return len(changed) == 0 && len(removed) == 0
}

// setupModule writes the given files into a new temporary module; the path is never printed.
func setupModule(files map[string]string) string {
	dir, err := os.MkdirTemp("", "publishing")
	if err != nil {
		return ""
	}
	for name, content := range files {
		os.MkdirAll(filepath.Dir(filepath.Join(dir, name)), 0o755)
		os.WriteFile(filepath.Join(dir, name), []byte(content), 0o644)
	}
	return dir
}

// compiles attempts to build the package in the given directory; the output is never printed.
func compiles(dir string) bool {
	cmd := exec.Command("go", "build", "-o", os.DevNull, "./...")
	cmd.Dir = dir
	cmd.Env = append(os.Environ(), "GOTOOLCHAIN=local")
	return cmd.Run() == nil
}
```

```go
// measurement1.go — version declared in the source, compatibility promise checked by a tool (signature comparison)
package main

import "fmt"

const versionV1 = "v1.0.0"
const versionV2Compatible = "v1.1.0"
const versionV2Incompatible = "v2.0.0"

const sourceV1 = "package sample\n\nfunc Double(x int) int { return x * 2 }\n"
const sourceV2Compatible = "package sample\n\nfunc Double(x int) int { return x * 2 }\n\nfunc Triple(x int) int { return x * 3 }\n"
const sourceV2Incompatible = "package sample\n\nfunc Double(x int, factor int) int { return x * factor }\n"

func main() {
	s1 := signatures(sourceV1)
	s2c := signatures(sourceV2Compatible)
	s2i := signatures(sourceV2Incompatible)

	fmt.Println("-- version in the source, compatibility from the tool --")
	show("version "+versionV1+" -> "+versionV2Compatible, "tool", fmt.Sprint("compatible=", compatible(s1, s2c)))
	show("version "+versionV1+" -> "+versionV2Incompatible, "tool", fmt.Sprint("compatible=", compatible(s1, s2i)))

	addedC, changedC, _ := compare(s1, s2c)
	show("added name count, compatible change", "tool", fmt.Sprint(len(addedC)))
	show("changed signature count, compatible change", "tool", fmt.Sprint(len(changedC)))

	_, changedI, _ := compare(s1, s2i)
	show("changed signature count, incompatible change", "tool", fmt.Sprint(len(changedI)))
}
```

```
-- version in the source, compatibility from the tool --
version v1.0.0 -> v1.1.0                       tool    compatible=true
version v1.0.0 -> v2.0.0                       tool    compatible=false
added name count, compatible change            tool    1
changed signature count, compatible change     tool    0
changed signature count, incompatible change   tool    1
```

**BD12.** All three version numbers are written in the source — this is the only fact in this
lesson that falls into the **source** class. But **what relationship** the versions hold to one
another — that `v1.1.0` does not break `v1.0.0`, that `v2.0.0` does — comes not from the numbers
themselves but from running the comparator over the signature sets. `v1.1.0` only adds a new
name, `Double`'s signature stays the same — this is why `compatible=true`. `v2.0.0` adds a second
parameter to `Double`; the name stays the same but the signature changed — this is why
`compatible=false`. The version numbers themselves (`v1.0.0`, `v1.1.0`, `v2.0.0`) are not a rule
but a **label**; the side that enforces the rule never reads the numbers, it only compares the
signature sets.

The **Go Fundamentals** course's Packages and Tooling topic measured which version of a module
gets selected, through the rule of minimal version selection; this lesson does not rebuild that
selection mechanism, it only measures the promise the **selected** version carries.

#### Verified with a Real Build

The verdict above is entirely static: no source was genuinely compiled, only parsed and compared.
Whether this verdict matches a real build is a separate question, and the measurement below tests
this directly — it genuinely compiles a source calling `v1.0.0` in its old form, together with
both the `v1.1.0` and the `v2.0.0` package.

```go
// measurement2.go — verifying the static comparison with a genuine build
package main

import "fmt"

const sourceV2Compatible = "package sample\n\nfunc Double(x int) int { return x * 2 }\n\nfunc Triple(x int) int { return x * 3 }\n"
const sourceV2Incompatible = "package sample\n\nfunc Double(x int, factor int) int { return x * factor }\n"

// setup writes the old caller's source together with the given package version into a temporary module.
func setup(packageSource string) string {
	return setupModule(map[string]string{
		"go.mod":            "module trial\n\ngo 1.24\n",
		"sample/sample.go":  packageSource,
		"caller.go":         "package main\n\nimport \"trial/sample\"\n\nfunc main() {\n\t_ = sample.Double(5)\n}\n",
	})
}

func main() {
	compatibleDir := setup(sourceV2Compatible)
	incompatibleDir := setup(sourceV2Incompatible)
	defer func() { _, _ = compatibleDir, incompatibleDir }()

	fmt.Println("-- static verdict, confirmed with a genuine build --")
	show("old caller + compatible version compiled", "tool", fmt.Sprint(compiles(compatibleDir)))
	show("old caller + incompatible version compiled", "tool", fmt.Sprint(compiles(incompatibleDir)))
}
```

```
-- static verdict, confirmed with a genuine build --
old caller + compatible version compiled       tool    true
old caller + incompatible version compiled     tool    false
```

**BD13.** Both lines match the previous measurement's verdict exactly: in the version pair called
`compatible=true`, the old caller genuinely compiles; in the pair called `compatible=false`, it
genuinely does not. This match is not a coincidence — the signature comparison already models the
compiler's own rule: for a call to compile, the called function's name and parameter list must
match what is expected at the call site. The static comparator applies this rule without ever
running the compiler; the real build applies the same rule once more, through a genuine call, this
time **without a run, but with a genuine tool call**. Just as with the linker options, two
different paths arrive at the same answer here as well; the difference is that one reads the
source without ever compiling it, the other genuinely compiles it.

The fact that these two paths confirm each other justifies an important design choice in
practice: the static comparison works without ever needing a genuine caller source — it only
reads the two versions' own source, no "old code" example is needed. A real build is the exact
opposite: it can say nothing without a specific caller, but what it says is directly tied to that
caller's fate. In a publishing process the first path provides a cheap, general pre-screen, the
second a definitive proof against a specific consumer; this lesson measures that both arrive at
the same two conclusions, showing that the cheap path can stand in for the expensive one in this
narrow example.

#### Limiting Measurement: The Promise Covers Signatures, Not Behavior

Both measurements above looked at signature changes. An implementation's **behavior** can change
even when a signature never changes at all — same name, same parameter, same return type, but a
different computation inside the body. The measurement below shows this directly: two real
functions named `Double` carry the identical signature, but one multiplies by two, the other by
three.

```go
// measurement3.go — limiting measurement: the compatibility promise covers signatures, not behavior
package main

import "fmt"

// doubleOld is the real implementation of the first version.
func doubleOld(x int) int { return x * 2 }

// doubleNewBehavior is a next-version implementation carrying the IDENTICAL signature but computing differently.
func doubleNewBehavior(x int) int { return x * 3 }

const sourceOld = "package sample\n\nfunc Double(x int) int { return x * 2 }\n"
const sourceNewBehavior = "package sample\n\nfunc Double(x int) int { return x * 3 }\n"

func main() {
	fmt.Println("-- limiting measurement: same signature, different behavior --")
	show("signature comparison", "tool", fmt.Sprint("compatible=", compatible(signatures(sourceOld), signatures(sourceNewBehavior))))
	show("Double(5) old implementation", "tool", fmt.Sprint(doubleOld(5)))
	show("Double(5) new implementation", "tool", fmt.Sprint(doubleNewBehavior(5)))
	show("same result from both implementations", "tool", fmt.Sprint(doubleOld(5) == doubleNewBehavior(5)))
}
```

```
-- limiting measurement: same signature, different behavior --
signature comparison                           tool    compatible=true
Double(5) old implementation                   tool    10
Double(5) new implementation                   tool    15
same result from both implementations          tool    false
```

**BD14.** The comparator classifies this change as `compatible=true`, because `Double`'s name,
parameter type, and return type never changed at all. But called with the same input (`5`), the
two implementations give different numbers — `10` and `15`. This is the limit of the
compatibility promise's **scope**: the promise says "your old code will keep compiling and
running," it does not say "your old code's **result** will stay the same." A caller's code
compiles and runs without trouble against both versions — no error, no panic — but the result it
produces changes with the version. The signature comparator can never see this difference at all,
because what it looks at is names and types; what is computed inside a body sits entirely outside
the layer this tool can reach.

The reason for this limit is not arbitrary: comparing a body's **meaning** between two versions
is a completely different problem from comparing its signature. Signature comparison was solved
in this lesson with a few lines of parsing and a dictionary comparison; behavior comparison, in
its general form, cannot be solved at all — testing whether two functions give the same result for
every input falls into a problem class that cannot be answered in finite time. This is why a
compatibility promise, in practice, is always built on signatures: something measurable,
decidable in finite time. The one remaining way to secure behavioral compatibility is the
table-driven tests and benchmarks measured in this course's first topic — running
the new version against the old version's expected inputs and comparing the results. This
completes what the signature comparator cannot do, through a genuine run; but that now falls
outside the `tool` class measured in this lesson, it belongs to the course's `run` class.

This shows that signature compatibility is a **necessary**, not a **sufficient**, condition in a
publishing process. If the signature is incompatible, the old code definitely does not compile —
this lesson confirmed this with a genuine build. If the signature is compatible, the old code
definitely does compile, but there is no guarantee whatsoever that the result it produces will be
the same as before. Raising a version number therefore carries two separate decisions at once:
whether the signatures changed (measurable by this lesson) and whether the behavior changed
(measurable only by a separate testing and observation process).

#### Summary

- A version number is declared openly in the source, but the compatibility promise it carries is
  checked not by the source itself but by a tool comparing the signatures of exported functions.
- A change that only adds a new name is counted compatible, a change that modifies an existing
  signature is counted incompatible; the verdict comes not from the version number itself but
  from comparing the signature sets.
- The static signature verdict was confirmed by genuinely compiling an old caller against the new
  version; both methods arrived at the same two conclusions.
- Limiting measurement: two implementations carrying the identical signature can give a
  completely different result, and the signature comparator never sees this difference at all.
- The compatibility promise guarantees that a source **compiles and runs**, it does not guarantee
  that the result it produces stays **the same** from version to version.

#### Next Step

The tool measured in this lesson read the source only at the level of names and types; it never
looked at what was inside the bodies at all. The next lesson descends into exactly this gap: a
tool that searches for specific patterns inside a source, reading the body itself rather than the
signature. The question is no longer "did the names and types change" but "is there a pattern
inside the source that has never been searched for before."

### Static Analysis and Security Scanning

Source: https://academia.sh/en/courses/go-production/static-analysis-and-security-scanning

Of the same two sources, one is clean, the other flawed; the compiler accepts both the same way, but an analyzer reports the difference between them through an exit code. Of three patterns, the compiler catches none, the analyzer catches two. The limiting measurement shows, by running it, that coming out clean from analysis does not mean flawless — the third, uncaught pattern genuinely crashes when run. A final measurement compares a dependency's declared version against a modeled threshold list.

The tool measured in the previous lesson read the source only at the level of names and types;
it never looked at what was inside the bodies at all. This lesson descends into exactly that gap:
a tool that reads the body itself, searching for specific patterns. But this tool has its own
limit too — reporting what it found does not mean that what it did not search for does not exist.

#### Two Sources, One Analyzer

**Static analysis** is a process that searches a source, without ever running it, only by reading
its text, for specific fault patterns. The language's own distribution's analysis tool does this
job with a single command and reports only an **exit code** — in this lesson we never read the
text of the lines it found, only whether the result is clean or flawed.

The measurement below sets up two sources: one prints an integer with the correct format
verb, the other gives a string to the same format verb — a mismatch between type and format.

```go
// helpers.go — the helpers this lesson shares: setting up a temporary package and calling the tool
package main

import (
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
)

// show prints a fact, who states it, and its evidence on a single line.
func show(info, speaker, evidence string) {
	fmt.Printf("%-40s %-7s %s\n", info, speaker, evidence)
}

// setupModule writes the given files into a new temporary module; the path is never printed.
func setupModule(files map[string]string) string {
	dir, err := os.MkdirTemp("", "analysis")
	if err != nil {
		return ""
	}
	os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module analysistrial\n\ngo 1.24\n"), 0o644)
	for name, content := range files {
		os.WriteFile(filepath.Join(dir, name), []byte(content), 0o644)
	}
	return dir
}

// runTool runs the language's own command, returning the output and whether it succeeded; output is never printed.
func runTool(dir string, arg ...string) (string, bool) {
	cmd := exec.Command("go", arg...)
	cmd.Dir = dir
	cmd.Env = append(os.Environ(), "GOTOOLCHAIN=local")
	output, err := cmd.CombinedOutput()
	return string(output), err == nil
}

// compiles attempts to build the package in the given directory.
func compiles(dir string) bool {
	_, ok := runTool(dir, "build", "-o", os.DevNull, ".")
	return ok
}

// analysisClean says whether the package in the given directory passes static analysis clean.
func analysisClean(dir string) bool {
	_, ok := runTool(dir, "vet", "./...")
	return ok
}
```

```go
// measurement1.go — same two sources, one clean one flawed, both passed through a single analyzer
package main

import "fmt"

func main() {
	clean := setupModule(map[string]string{
		"prog.go": "package main\n\nimport \"fmt\"\n\nfunc main() {\n\tfmt.Printf(\"value=%d\\n\", 7)\n}\n",
	})
	flawed := setupModule(map[string]string{
		"prog.go": "package main\n\nimport \"fmt\"\n\nfunc main() {\n\tfmt.Printf(\"value=%d\\n\", \"text\")\n}\n",
	})

	fmt.Println("-- two sources, one analyzer --")
	show("clean source compiled", "tool", fmt.Sprint(compiles(clean)))
	show("flawed source compiled", "tool", fmt.Sprint(compiles(flawed)))
	show("clean source analysis", "tool", fmt.Sprint("clean=", analysisClean(clean)))
	show("flawed source analysis", "tool", fmt.Sprint("clean=", analysisClean(flawed)))
}
```

```
-- two sources, one analyzer --
clean source compiled                    tool    true
flawed source compiled                   tool    true
clean source analysis                    tool    clean=true
flawed source analysis                   tool    clean=false
```

**BD15.** Both sources compile — the type mismatch is not a compiler-level error, because the
relationship between a format string and its arguments is not the subject of the compiler's type
checking. The analyzer, by contrast, does distinguish these two lines: in one the format verb and
the argument type match, in the other they do not. This is another form of the "tool" knowledge
seen in previous lessons — the analyzer searches for a pattern without running the source, only
by reading it, and reports what it found through a single exit code.

#### The Pattern the Compiler Does Not Catch

The single pattern above is only a beginning. The measurement below tries three separate patterns
together: a format mismatch, a copied lock value, and an ignored error. All three compile; the
question asked is how many are caught by the analyzer.

```go
// measurement2.go — three patterns the compiler rejects none of, splitting apart as they pass through the analyzer
package main

import "fmt"

const patternFormat = "package main\n\nimport \"fmt\"\n\nfunc main() {\n\tfmt.Printf(\"value=%d\\n\", \"text\")\n}\n"

const patternLock = "package main\n\nimport \"sync\"\n\ntype Counter struct {\n\tlock sync.Mutex\n\tvalue int\n}\n\nfunc copyIt(c Counter) Counter {\n\treturn c\n}\n\nfunc main() {\n\tcopyIt(Counter{})\n}\n"

const patternIgnoredError = "package main\n\nimport \"fmt\"\n\ntype Record struct{ Name string }\n\nfunc find(key string) (*Record, error) {\n\tif key == \"\" {\n\t\treturn nil, fmt.Errorf(\"empty key\")\n\t}\n\treturn &Record{Name: key}, nil\n}\n\nfunc main() {\n\tr, _ := find(\"\")\n\tfmt.Println(r.Name)\n}\n"

func main() {
	patterns := []struct{ name, source string }{
		{"format mismatch", patternFormat},
		{"lock copy", patternLock},
		{"ignored error", patternIgnoredError},
	}

	fmt.Println("-- three patterns the compiler does not catch --")
	compiledCount, cleanCount := 0, 0
	for _, p := range patterns {
		dir := setupModule(map[string]string{"prog.go": p.source})
		c := compiles(dir)
		cl := analysisClean(dir)
		if c {
			compiledCount++
		}
		if cl {
			cleanCount++
		}
		show(p.name+" compiled", "tool", fmt.Sprint(c))
		show(p.name+" analysis clean", "tool", fmt.Sprint(cl))
	}
	show("compiled count of three patterns", "tool", fmt.Sprint(compiledCount))
	show("analysis-passed count of three patterns", "tool", fmt.Sprint(cleanCount))
	show("pattern count caught by analyzer", "tool", fmt.Sprint(len(patterns)-cleanCount))
}
```

```
-- three patterns the compiler does not catch --
format mismatch compiled                 tool    true
format mismatch analysis clean           tool    false
lock copy compiled                       tool    true
lock copy analysis clean                 tool    false
ignored error compiled                   tool    true
ignored error analysis clean             tool    true
compiled count of three patterns         tool    3
analysis-passed count of three patterns  tool    1
pattern count caught by analyzer         tool    2
```

**BD16.** All three patterns compile — the number of patterns caught by the compiler is zero.
These three patterns sit entirely outside the layer the compiler works in, because all three are
**type-valid** code: a wrongly formatted print call, a struct copied by value with a lock field
inside, an ignored error value — none of these is a type mismatch or a syntax error. The analyzer
catches two of the three patterns: the format mismatch and the lock copy. The third — the ignored
error — comes out **clean** from analysis. This third pattern is the subject of the next section.

The two caught patterns share a common property: both fit a pattern that sits **outside** the
language's own type system, but is still mechanically recognizable. In the format mismatch, the
pattern is whether every `%` mark in a format string matches the type of the argument supplied;
in the lock copy, the pattern is whether a type documented by the `sync` package (any struct
containing a lock) is carried by pointer rather than by value. The analyzer enforces both these
rules by parsing the source and searching for a specific shape in the syntax tree — much like the
signature comparator of the previous lesson, except what is searched here is not a signature but
a usage pattern. The third pattern's failure to be caught is not because the analyzer fails to
match **either** of these two rules — it is because a rule searching for an ignored error is not
in the list at all.

#### Limiting Measurement: Coming Out Clean Does Not Mean Flawless

The third pattern above is a call that ignores a function's returned error and uses the result
directly. The analyzer found this clean; the compiler did not reject it either. The measurement
below genuinely runs this pattern — this time not by calling a subprocess, but by running the
same logic inside this very measurement, because the panic here will be caught with `recover` and
read only as a boolean.

```go
// measurement3.go — limiting measurement: coming out clean does not mean flawless
package main

import "fmt"

const patternIgnoredError = "package main\n\nimport \"fmt\"\n\ntype Record struct{ Name string }\n\nfunc find(key string) (*Record, error) {\n\tif key == \"\" {\n\t\treturn nil, fmt.Errorf(\"empty key\")\n\t}\n\treturn &Record{Name: key}, nil\n}\n\nfunc main() {\n\tr, _ := find(\"\")\n\tfmt.Println(r.Name)\n}\n"

// Record and find are a genuine, runnable counterpart of the source above: they carry the exact
// same logic that the analyzer read as text.
type Record struct{ Name string }

func find(key string) (*Record, error) {
	if key == "" {
		return nil, fmt.Errorf("empty key")
	}
	return &Record{Name: key}, nil
}

// attempt runs the call that ignores the error and catches the panic; the stack trace is never printed.
func attempt() (panicked bool) {
	defer func() {
		if r := recover(); r != nil {
			panicked = true
		}
	}()
	r, _ := find("")
	_ = r.Name
	return false
}

func main() {
	dir := setupModule(map[string]string{"prog.go": patternIgnoredError})

	fmt.Println("-- limiting measurement: coming out clean does not mean flawless --")
	show("analysis result", "tool", fmt.Sprint("clean=", analysisClean(dir)))
	show("panicked when run", "run", fmt.Sprint(attempt()))
}
```

```
-- limiting measurement: coming out clean does not mean flawless --
analysis result                          tool    clean=true
panicked when run                        run     true
```

**BD17.** The analyzer's answer (`clean=true`) and the run's answer (`panicked=true`) say two
separate things about the same source, and both are correct. The analyzer says "I found none of
the patterns I search for" — this does not include a rule searching for an ignored error. The run
says "this source, run with this input, crashes." These two answers do not contradict each other;
both are bounded by the tool's **scope**. The `clean=true` line therefore cannot be read as "this
source has no flaw" — it can only be read as "none of the patterns the analyzer knows about are
present in this source." This is the same distinction the course already established for coverage
ratio: a tool's silence does not mean the thing it **does not search for** does not exist.

The practical counterpart of this limit is that an analyzer's rule list is not **fixed**, but it
is always **finite**. As new patterns are noticed, new rules can be added to the list — the two
rules used specifically in this lesson (format mismatch, lock copy) were added this way too, entering
the list only after a real fault was seen repeatedly in real programs. But no matter how large the
list grows, there is always a pattern not yet added; this lesson chose a single example to make
this gap concrete, but the chosen example itself is not what matters — what matters is that an
analyzer's **clean** answer always means "clean according to the rules known so far," carrying no
claim of absolute correctness.

#### Known Vulnerability Scan: A Modeled List

The patterns the analyzer searches for are bounded by the language's own rules. A dependency's
known **vulnerability** is a separate kind of information — it comes not from the code's own
shape, but from **which version** the dependency was fixed in. A **known vulnerability scan**
compares a dependency's declared version against the version the fix arrived in. The measurement
below never connects to a real vulnerability list at all; the comparison is **modeled** in the
source with a small threshold table.

```go
// measurement4.go — known vulnerability scan, a tool built with a modeled threshold list
package main

import "fmt"

// Version carries a three-part version number (same pattern as M09/K01 go-packages/02).
type Version struct{ Major, Minor, Patch int }

func (v Version) text() string { return fmt.Sprintf("%d.%d.%d", v.Major, v.Minor, v.Patch) }

func (v Version) lessThan(other Version) bool {
	if v.Major != other.Major {
		return v.Major < other.Major
	}
	if v.Minor != other.Minor {
		return v.Minor < other.Minor
	}
	return v.Patch < other.Patch
}

// safeThreshold is a MODELED table holding the known lowest safe version for each module; not a
// real vulnerability database, only a small list this measurement itself sets up.
var safeThreshold = map[string]Version{
	"libraryX": {1, 4, 0},
	"libraryY": {2, 0, 0},
}

type Dependency struct {
	Module   string
	Declared Version
}

// hasVulnerability checks whether the declared version falls below the modeled threshold.
func hasVulnerability(d Dependency) bool {
	threshold, exists := safeThreshold[d.Module]
	if !exists {
		return false
	}
	return d.Declared.lessThan(threshold)
}

func main() {
	dependencies := []Dependency{
		{Module: "libraryX", Declared: Version{1, 2, 0}},
		{Module: "libraryY", Declared: Version{2, 3, 0}},
	}

	fmt.Println("-- known vulnerability scan: a modeled list --")
	vulnerableCount := 0
	for _, d := range dependencies {
		v := hasVulnerability(d)
		if v {
			vulnerableCount++
		}
		show(d.Module+" declared="+d.Declared.text(), "tool", fmt.Sprint("vulnerable=", v))
	}
	show("vulnerable count of two dependencies", "tool", fmt.Sprint(vulnerableCount))
}
```

```
-- known vulnerability scan: a modeled list --
libraryX declared=1.2.0                  tool    vulnerable=true
libraryY declared=2.3.0                  tool    vulnerable=false
vulnerable count of two dependencies     tool    1
```

**BD18.** The declared version for `libraryX` (`1.2.0`) falls below the modeled threshold
(`1.4.0`) — vulnerable. The declared version for `libraryY` (`2.3.0`) exceeds its threshold
(`2.0.0`) — not vulnerable. This comparison carries the same limit as the previous two
measurements: the threshold table itself **is a model this measurement built**, it does not come
from a real record, and if a module is not in the table at all, `hasVulnerability` silently
returns `false` — a missing table means the scan finds nothing, exactly like the analyzer never
seeing a pattern it does not search for. The **Go Fundamentals** course's minimal version
selection measurement established which version gets selected; this measurement asks, through a
separate, modeled comparison, whether that selected version falls below a known threshold.

#### Summary

- Static analysis works by searching for specific patterns without ever running the source; it
  reports the difference between two sources the compiler accepts only through an exit code.
- Of the three patterns the compiler rejects none of, two were caught by the analyzer; one — the
  ignored error — came out clean.
- Limiting measurement: the pattern that came out clean crashed when genuinely run; the
  analyzer's silence means not flawlessness, only that the pattern searched for was not found.
- A known vulnerability scan compares a dependency's declared version against a modeled
  threshold; if the table is missing an entry, the scan finds nothing at all.
- The measure used throughout this lesson stayed the same: a tool's silence shows that what it
  searched for was not found, not that the thing itself does **not exist**.

#### Next Step

The analyzer measured in this lesson stayed within the language's own boundaries — the patterns
it searched for always belonged to Go's own syntax and types. The course's closing lesson crosses
this boundary: when a Go program calls another language's library, one more boundary opens, one
the analyzer cannot see through either. The next question is which tool, with which number, the
cost of crossing the language's own boundary becomes visible through.

### C Interoperability

Source: https://academia.sh/en/courses/go-production/c-interoperability

The same task is compared in two forms — the language's own code and a C function called through cgo — and both give the same allocation count (1). The package with the native call does not compile with CGO_ENABLED=0, does not cross-compile, and escape analysis makes no statement about the C function's body (0/21 lines). Limiting measurement: memory not freed on the C side shows up in neither the analyzer nor the allocation count — the cost is not in the call itself, but in what the tool can say. The course, and the M09 Go curriculum, close with this lesson through a sixteen-row table.

The previous lesson measured that an analyzer coming out clean does not mean flawless, only
that the searched pattern was absent — its silence was a limit of the rule list, not proof. This
lesson carries that limit further: once a Go program steps outside the language's boundary into
another language's code, the compiler, the analyzer, and the profiler can no longer always reach
it. The question is narrow: through which tool, with which number, does crossing that boundary's
cost become visible?

C here is not a product or a library brand — it is a language, its name written the same way
Go's own is. But the mechanism used throughout this lesson, cgo, carries a dependency the rest of
the course never carried: the source only compiles with a genuine C compiler present and
`CGO_ENABLED` on. **BD19.** This lesson carries that dependency as an open assumption; without a
compiler, or with `CGO_ENABLED` off, the blocks do not compile at all. None of the course's
fifteen sibling lessons needed this second chain.

#### cgo: A Call That Crosses the Language's Boundary

The standard library opens this crossing through a pseudo-package: right before `import "C"`, a
gapless comment block carries genuine C source, which the compiler hands to a C compiler and
binds back to the Go side. The `native.go` below defines a small C function (`upperC`) that
upper-cases a string using `<string.h>` and `<ctype.h>`, called from two Go wrappers.

```go
// native.go — a native library wrapper calling a function written in C through cgo
package main

/*
#include <string.h>
#include <stdlib.h>
#include <ctype.h>

char* upperC(const char* s) {
	size_t n = strlen(s);
	char* result = (char*)malloc(n + 1);
	for (size_t i = 0; i < n; i++) {
		result[i] = toupper((unsigned char)s[i]);
	}
	result[n] = 0;
	return result;
}
*/
import "C"
import "unsafe"

// upperClean passes the string to the C side's upperC function and gets the result back;
// it frees the result buffer allocated on the C side.
func upperClean(s string) string {
	cs := C.CString(s)
	defer C.free(unsafe.Pointer(cs))
	result := C.upperC(cs)
	defer C.free(unsafe.Pointer(result))
	return C.GoString(result)
}

// upperLeaking makes the same call but never frees the C side's result buffer.
func upperLeaking(s string) string {
	cs := C.CString(s)
	defer C.free(unsafe.Pointer(cs))
	result := C.upperC(cs)
	return C.GoString(result)
}
```

`C.CString` copies the given Go string with **C's own allocator** (`malloc`) — a region Go's
garbage collector never tracks. `C.GoString` works the other way: it reads bytes on the C side
and builds a new string on Go's heap. `upperClean` frees both C buffers (`cs`, `result`) with
`C.free`; `upperLeaking` never frees the second — measured in a later section.

The rest of this lesson reuses the same two routines the previous lessons shared: `setupModule`
sets up a temporary module, `runTool` runs the language's own command over it.

```go
// tools.go — the helpers this lesson shares: setting up a temporary package, calling the tool, running five times
package main

import (
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"testing"
)

// show prints a fact, who states it, and its evidence on a single line.
func show(info, speaker, evidence string) {
	fmt.Printf("%-48s %-7s %s\n", info, speaker, evidence)
}

// setupModule writes the given files into a new temporary module; the path is never printed.
func setupModule(files map[string]string) string {
	dir, err := os.MkdirTemp("", "nativelibrary")
	if err != nil {
		return ""
	}
	os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module nativetrial\n\ngo 1.24\n"), 0o644)
	for name, content := range files {
		os.WriteFile(filepath.Join(dir, name), []byte(content), 0o644)
	}
	return dir
}

// runTool runs the language's own command, returning whether the exit succeeded; output is never printed.
func runTool(dir string, extraEnv []string, arg ...string) (string, bool) {
	cmd := exec.Command("go", arg...)
	cmd.Dir = dir
	cmd.Env = append(append(os.Environ(), "GOTOOLCHAIN=local"), extraEnv...)
	output, err := cmd.CombinedOutput()
	return string(output), err == nil
}

// fiveRuns runs the same benchmark five times, returning the first value and how
// many distinct values were seen — the course's stability pattern for proving a number.
func fiveRuns(f func(*testing.B)) (int64, int) {
	values := map[int64]bool{}
	var first int64
	for i := 0; i < 5; i++ {
		r := testing.Benchmark(f)
		values[r.AllocsPerOp()] = true
		if i == 0 {
			first = r.AllocsPerOp()
		}
	}
	return first, len(values)
}
```

#### Same Task, Two Forms: Allocation Count

The pattern from the course's Benchmarking lesson continues unchanged: two forms of
the same task, each across five runs, compared only by allocation count — one the language's own
code (`strings.ToUpper`), the other a native library call through `upperClean`.

```go
// measurement1.go — same task, two forms: the language's own code and a native library call, compared by allocation count
package main

import (
	"fmt"
	"strings"
	"testing"
)

var sampleString = "screw-nut-washer"
var sink string

//go:noinline
func benchGo(b *testing.B) {
	for i := 0; i < b.N; i++ {
		sink = strings.ToUpper(sampleString)
	}
}

//go:noinline
func benchNative(b *testing.B) {
	for i := 0; i < b.N; i++ {
		sink = upperClean(sampleString)
	}
}

func main() {
	fmt.Println("-- same task, two forms: allocation count --")
	goAllocs, goDistinct := fiveRuns(benchGo)
	nativeAllocs, nativeDistinct := fiveRuns(benchNative)
	show("language's own code (strings.ToUpper)", "run", fmt.Sprint(goAllocs, " distinct value across five runs=", goDistinct))
	show("native library call (cgo)", "run", fmt.Sprint(nativeAllocs, " distinct value across five runs=", nativeDistinct))
}
```

```
-- same task, two forms: allocation count --
language's own code (strings.ToUpper)            run     1 distinct value across five runs=1
native library call (cgo)                        run     1 distinct value across five runs=1
```

**BD20.** Both forms reach the same number, one, unchanged across five runs. This could read as
"the native call has no cost," but `upperClean`'s body does two things `strings.ToUpper` does
not: it copies the string into C's allocator (`CString`), allocates and fills a second buffer on
the C side (`upperC`), and frees both. The single `1` only counts the final copy `GoString`
writes onto Go's heap — C's `malloc` calls never enter this number, since the measuring tool
tracks only **its own** heap.

#### The Tool Knowledge a Native Call Costs

How much of what the course writes to the **tool** class — compilability, cross-compilation, the
compiler's decision — survives once a native call is added? The measurement below writes
`nativeSource` to a temporary module and asks three things: does it compile with
`CGO_ENABLED=0`, does it cross-compile for a platform with no copy (`linux/arm64`), and how many
escape diagnostics belong to the Go side versus `upperC`.

```go
// measurement2.go — the tool knowledge a native call costs: compilability, cross-compilation, escape boundary
package main

import (
	"fmt"
	"os"
	"strings"
)

const nativeSource = "package main\n\n/*\n#include <string.h>\n#include <ctype.h>\n#include <stdlib.h>\n\nchar* upperC(const char* s) {\n\tsize_t n = strlen(s);\n\tchar* result = (char*)malloc(n + 1);\n\tfor (size_t i = 0; i < n; i++) result[i] = toupper((unsigned char)s[i]);\n\tresult[n] = 0;\n\treturn result;\n}\n*/\nimport \"C\"\nimport \"unsafe\"\n\nfunc upperNative(s string) string {\n\tcs := C.CString(s)\n\tdefer C.free(unsafe.Pointer(cs))\n\tresult := C.upperC(cs)\n\tdefer C.free(unsafe.Pointer(result))\n\treturn C.GoString(result)\n}\n\nfunc main() {\n\t_ = upperNative(\"x\")\n}\n"

func main() {
	dir := setupModule(map[string]string{"native.go": nativeSource})

	fmt.Println("-- the tool knowledge a native call costs --")
	_, disabledCompiled := runTool(dir, []string{"CGO_ENABLED=0"}, "build", "-o", os.DevNull, ".")
	show("compiled with CGO_ENABLED=0", "tool", fmt.Sprint(disabledCompiled))

	_, crossCompiled := runTool(dir, []string{"CGO_ENABLED=1", "GOOS=linux", "GOARCH=arm64"}, "build", "-o", os.DevNull, ".")
	show("compiled for the linux/arm64 target", "tool", fmt.Sprint(crossCompiled))

	output, _ := runTool(dir, []string{"CGO_ENABLED=1"}, "build", "-gcflags=-m", "-o", os.DevNull, ".")
	lines := strings.Split(strings.TrimSpace(output), "\n")
	goSide := 0
	for _, l := range lines {
		if strings.Contains(l, "native.go") {
			goSide++
		}
	}
	cSide := strings.Count(output, "upperC")
	show("total escape/compile diagnostic lines", "tool", fmt.Sprint(len(lines)))
	show("diagnostics tied to a native.go line (Go side)", "tool", fmt.Sprint(goSide))
	show("diagnostics tied to upperC's body (C side)", "tool", fmt.Sprint(cSide))
}
```

```
-- the tool knowledge a native call costs --
compiled with CGO_ENABLED=0                      tool    false
compiled for the linux/arm64 target              tool    false
total escape/compile diagnostic lines            tool    21
diagnostics tied to a native.go line (Go side)   tool    11
diagnostics tied to upperC's body (C side)       tool    0
```

**BD21.** The package does not compile with `CGO_ENABLED=0`: a file carrying `import "C"` never
enters the build once cgo is off — another form of the Build Tags lesson's "a file stays outside
the build," this time chosen by an environment variable, not a comment line. **BD22.** The
Cross-Compilation lesson measured that a source with no platform-specific call compiles on all
three targets; here the uncopied `linux/arm64` build fails, because what is needed is not Go's
own cross-compiler but that target's **own** C compiler, missing from this environment.

**BD23.** Eleven of twenty-one escape diagnostics tie back to a line in `native.go`. **None** —
zero — carry `upperC`'s name: not because the compiler ignores this function, but because it
genuinely **cannot see** it — escape analysis runs over Go's own syntax tree, and `upperC`'s body
is C. The compiler decides up to the boundary (the `cs` and `result` pointers) and asks nothing
beyond it.

#### Limiting Measurement: The Cost Is Not in the Call Itself

The measurements above point the same way: escape analysis does not see past the boundary,
compilability depends on a C compiler, allocation count counts only Go's own heap. This final
measurement joins all three into a sharper example: the only difference between `upperClean` and
`upperLeaking` is whether the C-side result buffer gets freed — a genuine leak in C's own heap.

```go
// boundary.go — limiting measurement: the cost is not in the call itself, but in what the tool can say
package main

import (
	"fmt"
	"testing"
)

const leakingSource = "package main\n\n/*\n#include <string.h>\n#include <ctype.h>\n#include <stdlib.h>\n\nchar* upperC(const char* s) {\n\tsize_t n = strlen(s);\n\tchar* result = (char*)malloc(n + 1);\n\tfor (size_t i = 0; i < n; i++) result[i] = toupper((unsigned char)s[i]);\n\tresult[n] = 0;\n\treturn result;\n}\n*/\nimport \"C\"\nimport \"unsafe\"\n\nfunc upperLeaking(s string) string {\n\tcs := C.CString(s)\n\tdefer C.free(unsafe.Pointer(cs))\n\tresult := C.upperC(cs)\n\treturn C.GoString(result)\n}\n\nfunc main() {\n\t_ = upperLeaking(\"x\")\n}\n"

var sink string
var sampleString = "screw-nut-washer"

//go:noinline
func benchClean(b *testing.B) {
	for i := 0; i < b.N; i++ {
		sink = upperClean(sampleString)
	}
}

//go:noinline
func benchLeaking(b *testing.B) {
	for i := 0; i < b.N; i++ {
		sink = upperLeaking(sampleString)
	}
}

func main() {
	fmt.Println("-- limiting measurement: does a memory leak show up when run --")
	cleanAllocs, cleanDistinct := fiveRuns(benchClean)
	leakingAllocs, leakingDistinct := fiveRuns(benchLeaking)
	show("allocation count of the freeing version", "run", fmt.Sprint(cleanAllocs, " distinct value across five runs=", cleanDistinct))
	show("allocation count of the leaking version", "run", fmt.Sprint(leakingAllocs, " distinct value across five runs=", leakingDistinct))

	dir := setupModule(map[string]string{"native.go": leakingSource})
	_, clean := runTool(dir, []string{"CGO_ENABLED=1"}, "vet", "./...")
	show("analyzer finds the leaking version clean too", "tool", fmt.Sprint(clean))
}
```

```
-- limiting measurement: does a memory leak show up when run --
allocation count of the freeing version          run     1 distinct value across five runs=1
allocation count of the leaking version          run     1 distinct value across five runs=1
analyzer finds the leaking version clean too     tool    true
```

**BD24.** The two numbers are identical — freeing and leaking both give one allocation, a single
value across five runs. Because the allocation count tracks only Go's heap, the leak growing on
the C side is never visible in it. The analyzer gives the same silence: `vet` finds the leaking
version clean too, since none of its patterns cover failing to free a C call's result — nor could
it, the leak sitting inside `upperC`'s body, entirely outside Go's syntax. The course's measuring
axis reaches its own boundary here: all three answers (source, tool, run) stayed within one
boundary, never reaching the C compiler's decisions beyond it. The cost is not in the call
itself, but in what the tool **can say**: neither compiler, analyzer, nor profiler sees past the
boundary.

#### Course Wrap-Up

This course carried a single question across sixteen lessons: who states a fact about a program —
the source, a tool, or a run? The shared definition's three claims added three layers. First,
most of what can be known about a program is not written in the source: only two of eleven facts
sit in the text, the rest come from a tool or a run. Second, tool and run knowledge do not carry
the same reliability — a tool needs no run and gives the same answer every call, while run
knowledge must be separately tested for staying the same. Third, specific to this course: of the
the benchmark's two numbers, only the allocation count is repeatable; duration was
never written, printed, or measured.

This final lesson carried the same three classes past the language's boundary: escape analysis
says nothing about the C function's body, the analyzer does not see a leak in C, the allocation
count does not count C's allocator. The course's reading of "a tool speaking is not the same as
speaking truly" reaches its sharpest form here: past the boundary, both tool and run fall silent,
and that silence is not proof of flawlessness — it is proof of a place the tool never looked.

The course closes without overreaching: what was measured was a single native library call, not
the whole of cgo's machinery; the same boundary can show up with a different tool at a different
crossing. The M09 Go curriculum closes here too, and its five courses' five questions grow from
the same root: Go Fundamentals asked what stands in for the unwritten — defined, rejected, or
left open; Interfaces, Errors and Generics asked where a behavior's binding to a type is
established; Concurrency asked how a goroutine stops; The Standard Library and Network
Applications asked what happens to a value's type information at a boundary; this course asked
which source the knowledge comes from. All five converge on one observation: knowing a program
takes more than reading its source.

| Lesson | Fact measured | Speaker | Limiting measurement |
|---|---|---|---|
| Test Package Basics | Whether the test passed | run | A passing test proves only what it tested, not correctness |
| Table-Driven Tests | Whether adding a case changed coverage | run | The sixth case left coverage fixed at 100% |
| HTTP Tests | Which status code a request turned into | run | The request address is fixed — the handler is tested, not the network |
| Faking and Interface Separation | The interface separation that makes faking possible | source | Under excessive reduction the real implementation rejected, the fake accepted |
| Benchmarking | The distribution of eleven facts, allocation count | source · tool · run (2/5/4) | Per-operation and total allocation are separate; conflating them misleads |
| Coverage Reporting | The coverage ratio three test sets gave | run | An uninspecting case gave 100% with flawed source too |
| Profiling | The allocation count of two call forms | run | A value allocated on a never-run branch is invisible in the profile |
| Escape Analysis | The decision whether a value is allocated on the stack or the heap | tool | Not the language's promise — the compiler's own implementation detail |
| Garbage Collection Behavior | The effect of three techniques on allocation count | run | A readable and an unreadable form reached the same number |
| Tracing | The event count of two setups | run | Collecting a trace changes behavior; numbers are read within that limit |
| Build Tags | Which files enter the build | tool | An outside file is not deleted, it stays unseen until tagged |
| Cross-Compilation | Whether the source compiles for three targets | tool | Compiled does not mean it runs — the assumption depends on the target |
| Linker Options | A sourceless value embedded into the binary | tool | The linker never validates the embedded value, it accepts emptiness too |
| Module Publishing | Whether the version is compatible at the signature level | source · tool | Same signature, different result — the promise is in signatures, not behavior |
| Static Analysis and Security Scanning | Whether the difference was caught by the analyzer | tool | A clean-looking pattern crashed when run — clean is not flawless |
| C Interoperability | Allocation count, compilability, escape visibility of a native call | tool · run | Leaked C memory shows up in neither the analyzer nor the allocation count |

#### Summary

- Two forms — the language's own code, a C function called through cgo — reached the same
  allocation count (one), but this equality never counted the two `malloc` calls on the C side.
- A package with a native call did not compile with `CGO_ENABLED=0` and did not cross-compile for
  a target with no C compiler; the lesson carried this dependency as an open assumption.
- Eleven of twenty-one escape diagnostics tied to the Go side, zero to the C function's body — the
  compiler cannot see past the boundary, it does not ignore it.
- Limiting measurement: memory not freed on the C side shows up in neither the analyzer nor the
  allocation count; the cost is not in the call itself, it is in what the tool can say.
- The M09 Go curriculum closes with this lesson; the five courses' five questions — what stands in
  for the unwritten, where the binding sits, how a goroutine stops, type information at a
  boundary, who states this fact — converge on one observation: knowing a program takes more than
  reading its source.

