---
title: 'Interfaces, Errors and Generics'
source: 'https://academia.sh/en/courses/go-type-system'
language: en
updated: '2026-09-14T10:59:33+00:00'
license: 'CC BY-SA 4.0'
lessons: 13
outcomes:
    - 'Designs interfaces with the implicit implementation model'
    - 'Carries errors upward by wrapping them without losing context'
    - 'Defines type constraints with generics'
    - 'Separates by measuring whether a binding is established in the declaration, at the use site or in the value'
---

# Interfaces, Errors and Generics

Nowhere in Go is it written that a type satisfies an interface: the course measures every mechanism with a single question — where is this binding established, in a declaration in the source, at the use site the compiler looks at, or inside the value at run time? Interfaces, error values and generics all pass through the same frame.

## Interfaces

A type never writing anywhere in the source that it satisfies an interface, and the small-interface principle being a consequence of this; the empty interface carrying type information into runtime and the cost of that; the question a type assertion and a type switch ask of a value; contract composition through embedding; the measurable counterpart of defining an interface on the consumer side.

### The Interface Concept

Source: https://academia.sh/en/courses/go-type-system/interface-concept

4 of sixteen bindings are established in a declaration, 4 at a use site, 8 in the value — the course's binding split is produced here. The same type satisfies four interfaces and its source has zero references to any of them; the small-interface principle is not advice here but an observation measured by the count of satisfying types falling as method count rises. Limiting measurement: a compile-time conformance proof moves that same binding into the declaration, and the compiler stays silent when the proof is missing.

The Go Fundamentals course closed by asking who decides a name's visibility, a dependency's
version, and the enforcement of a formatting rule — all three answers were the language
itself: exporting depends on a letter's case, version selection on the lowest request written
in the module file, formatting rule on the formatter itself. This lesson carries the same
question one step further in. When a type satisfies an interface, there is no single line
that says so — not in the interface's definition, not in the type's own source. The compiler
knows this binding; someone reading the source cannot see it. So where is this binding
established?

This course asks that question sixteen times — across interfaces, error values, generics —
and each time writes the answer into one of three places: is the binding explicitly written
in the source (**declaration**), does the compiler establish it at the point a value is used
(**use site**), or is the binding carried inside the value itself at runtime (**value**)? This
lesson counts these three places for the first time and produces the number the rest of the
course will refer back to.

#### Where a Binding Is Established

An interface defines a method set and does nothing on its own — it carries no field, produces
no behavior. A type **satisfies** an interface if it meets that entire method set with its own
methods. Satisfaction being **implicit** means that no word in the type's source declares this.
A type can even satisfy an interface its own author never heard of; the only condition is that
the method signatures match.

Satisfaction itself is never written in the source. But a **proof that verifies** this
satisfaction can be added to the source — and then the binding moves into the declaration.
This distinction is the topic of the lesson's last section. For now, a verbal definition of
the three classes is enough: which binding stands explicitly in the source, which is
established by the compiler at the point of use, which is carried along with the value at
runtime — the measurement makes this **countable**; we already know this information from the
start.

#### The Frame: An Observer That Classifies a Binding

The measurement runs through a small observer that classifies each binding with a single
question. Three package-level counters (`declaration`, `useSite`, `value`) write every
observation to a class; `observe` is the single entry point, `summarize` prints the total.

```go
// binding.go — where is a binding established: in a declaration, at a use site, in the value
package main

import "fmt"

var declaration, useSite, value int

func observe(name, location, evidence string) {
	switch location {
	case "declaration":
		declaration++
	case "use":
		useSite++
	default:
		value++
	}
	fmt.Printf("%-40s %-12s %s\n", name, location, evidence)
}

func summarize() {
	fmt.Printf("binding: %d declaration, %d use site, %d value\n", declaration, useSite, value)
}
```

**IN1.** `observe` takes each line as three parts: a name, a class, a piece of evidence. The
evidence column carries the real value backing the claim — which line belongs to which class
is already known ahead of time; the frame does not produce that decision, it only makes it
**countable**. Keeping the counters at package level means the total stays consistent with the
observations whenever `summarize` is called.

#### Sixteen Bindings

This frame asks sixteen bindings in order, inside a single package. The measurement opens with
interface satisfaction, continues with embedding and a compile-time proof, then moves through
the dynamic type of a value placed in the empty interface, the question a type assertion and a
type switch ask of a value, an interface defined on the consumer side, an error value's
implicit satisfaction, a sentinel error's identity, the context a wrapped error carries, and
closes with a generic function's inferred type parameter, a type constraint's declaration, and
the instantiation of a generic type.

```go
// measurement.go — sixteen bindings, split into three locations
package main

import (
	"errors"
	"fmt"
)

type Writer interface{ Write(m string) string }
type Closer interface{ Close() error }

// Short, an interface defined on the consumer side: the producer type never mentions it.
type Short interface{ Write(m string) string }

// Embedded combines two contracts in a declaration.
type Embedded interface {
	Writer
	Closer
}

type File struct{ Name string }

func (f File) Write(m string) string { return f.Name + ":" + m }
func (f File) Close() error          { return nil }

// Compile-time conformance proof: the writer put this in a declaration.
var _ Embedded = File{}

type Record struct{ Number int }

func (r *Record) Failure() string { return "record" }

var ErrNotFound = errors.New("not found")

type FieldError struct{ Field string }

func (e *FieldError) Error() string { return "invalid field: " + e.Field }

func search(found bool) error {
	if !found {
		return fmt.Errorf("search failed: %w", ErrNotFound)
	}
	return nil
}

func validate() error {
	return fmt.Errorf("record could not be validated: %w", &FieldError{Field: "name"})
}

func first[T any](d []T) (T, bool) {
	var zero T
	if len(d) == 0 {
		return zero, false
	}
	return d[0], true
}

type Numeric interface{ ~int | ~float64 }

func sum[T Numeric](d []T) T {
	var t T
	for _, e := range d {
		t += e
	}
	return t
}

type Stack[T any] struct{ items []T }

func (s *Stack[T]) Add(o T) { s.items = append(s.items, o) }
func (s *Stack[T]) Type() string {
	var zero T
	return fmt.Sprintf("%T", zero)
}

func main() {
	fmt.Println("-- where is a binding established --")

	var w Writer = File{Name: "note"}
	observe("a type satisfying an interface", "use", "not written anywhere in the source")
	observe("compile-time conformance proof", "declaration", "var _ Embedded = File{}")
	observe("interface embedding", "declaration", "Embedded = Writer + Closer")

	observe("an interface value's dynamic type", "value", fmt.Sprintf("%T", w))

	c, ok := w.(Closer)
	observe("a type assertion's answer", "value", fmt.Sprint(ok, " ", c != nil))

	var kind string
	switch w.(type) {
	case File:
		kind = "File"
	default:
		kind = "unknown"
	}
	observe("the branch a type switch selects", "value", kind)

	var _ Short = File{}
	observe("an interface defined by the consumer", "use",
		"File satisfies four interfaces, its source has zero references to them")

	var empty any = w
	observe("a value's type in the empty interface", "value", fmt.Sprintf("%T", empty))

	var r *Record
	var h any = r
	observe("an interface carrying a nil pointer", "value", fmt.Sprint("pointer nil=", r == nil, " interface nil=", h == nil))

	var err error = &FieldError{Field: "name"}
	observe("the error interface being satisfied", "use", fmt.Sprintf("%T", err))

	wrapped := search(false)
	observe("a sentinel error's identity", "value", fmt.Sprint(errors.Is(wrapped, ErrNotFound)))
	observe("the text carried by wrapping", "value", wrapped.Error())

	var target *FieldError
	observe("extracting a type from a wrapped chain", "value", fmt.Sprint(errors.As(validate(), &target), " ", target.Field))

	f, found := first([]string{"a", "b"})
	observe("a generic function's type inference", "use", fmt.Sprint(f, " ", found))

	observe("a type constraint's declaration", "declaration", fmt.Sprint("Numeric sum=", sum([]int{1, 2, 3})))

	var stack Stack[float64]
	stack.Add(1.5)
	observe("instantiating a generic type", "declaration", "Stack[float64] -> "+stack.Type())

	summarize()
}
```

```
-- where is a binding established --
a type satisfying an interface           use          not written anywhere in the source
compile-time conformance proof           declaration  var _ Embedded = File{}
interface embedding                      declaration  Embedded = Writer + Closer
an interface value's dynamic type        value        main.File
a type assertion's answer                value        true true
the branch a type switch selects         value        File
an interface defined by the consumer     use          File satisfies four interfaces, its source has zero references to them
a value's type in the empty interface    value        main.File
an interface carrying a nil pointer      value        pointer nil=true interface nil=false
the error interface being satisfied      use          *main.FieldError
a sentinel error's identity              value        true
the text carried by wrapping             value        search failed: not found
extracting a type from a wrapped chain   value        true name
a generic function's type inference      use          a true
a type constraint's declaration          declaration  Numeric sum=6
instantiating a generic type             declaration  Stack[float64] -> float64
binding: 4 declaration, 4 use site, 8 value
```

Of sixteen bindings, **4 are established in a declaration, 4 at a use site, 8 in the value**.
This split is binding for the rest of the course; later lessons will print the lines of their
own slice and refer back to this table in a single sentence. Eight sitting in the value comes
from a Go interface value being a **pair** — it carries both a concrete type and a value
inside it; a type assertion, a type switch, comparing a sentinel error, and extracting a type
from a wrapped chain are all four readings of that same pair. The real subject of this lesson
is the first two lines and the seventh: interface satisfaction itself and its being defined on
the consumer side.

#### Implicit Satisfaction: Four Interfaces, Zero References

`File`'s source is three lines: one field, two methods (`Write`, `Close`). These two method
lines satisfy four separate interfaces — `Writer` (wants the `Write` method), `Closer` (wants
the `Close` method), `Embedded` (wants both, by embedding them), and `Short` (wants only
`Write`, a separate definition carrying the same signature as `Writer`). None of these four
interface names ever appear in `File`'s own source — not in the `type File struct` line, not
in the bodies of the `Write` and `Close` methods. Zero references in the source, four
satisfactions.

This is the concrete result of implicit satisfaction: a type can satisfy an interface its own
author never even saw, because the only condition for satisfaction is that the method
signatures match. The `Short` interface was defined **after** `Writer` in `measurement.go`,
and `File` was written with no knowledge of it; despite this, the line `var _ Short = File{}`
compiles. The seventh row of the table reduces this to a single number: four interfaces, zero
references. This observation is the first appearance of consumer-side definition, the topic of
the fifth lesson — there the same idea will turn into a design decision.

#### The Small-Interface Principle: Measurement, Not Advice

As an interface's method count rises, the number of types satisfying it is expected to fall —
wanting more methods means a stricter condition. This is the intuition the Design Principles
course's interface-segregation principle rests on; here it is measured not as advice but as a
countable relationship.

```go
// small-interface.go — as an interface's method count grows, does the number of types satisfying it shrink
package main

import "fmt"

type OneMethod interface{ Read() string }
type TwoMethod interface {
	Read() string
	Write(m string)
}
type ThreeMethod interface {
	Read() string
	Write(m string)
	Close() error
}

type ReadOnly struct{}

func (ReadOnly) Read() string { return "read" }

type ReadWrite struct{}

func (ReadWrite) Read() string  { return "read" }
func (ReadWrite) Write(m string) {}

type ReadWriteClose struct{}

func (ReadWriteClose) Read() string   { return "read" }
func (ReadWriteClose) Write(m string) {}
func (ReadWriteClose) Close() error   { return nil }

type WriteOnly struct{}

func (WriteOnly) Write(m string) {}

func main() {
	types := []any{ReadOnly{}, ReadWrite{}, ReadWriteClose{}, WriteOnly{}}

	oneCount, twoCount, threeCount := 0, 0, 0
	for _, t := range types {
		if _, ok := t.(OneMethod); ok {
			oneCount++
		}
		if _, ok := t.(TwoMethod); ok {
			twoCount++
		}
		if _, ok := t.(ThreeMethod); ok {
			threeCount++
		}
	}
	fmt.Println("types satisfying the one-method interface  :", oneCount)
	fmt.Println("types satisfying the two-method interface  :", twoCount)
	fmt.Println("types satisfying the three-method interface:", threeCount)
}
```

```
types satisfying the one-method interface  : 3
types satisfying the two-method interface  : 2
types satisfying the three-method interface: 1
```

**IN2.** Four types were built deliberately: three carry `Read` with growing method sets, the
fourth (`WriteOnly`) never carries `Read` at all and falls outside all three interfaces. Had
this last type not existed, the decline would still show, but the observation "there is also a
type that satisfies none of the interfaces" would be lost — the measurement keeps all four
together for this reason.

Tested against three interfaces, the four types' count falls to **3, 2, 1**: as the method set
grows from one method to three, the number of the same four types able to satisfy it shrinks.
This binding is the same "use site" class seen in the table — each count is established at the
line where a type assertion is called, not in the type's or the interface's own definition. A
small interface means an interface that wants few methods; an interface that wants few methods
means an interface most types satisfy. This is not a design tip, it is the direct result of
the count above.

#### Limiting Measurement: Conformance Can Also Be Written in the Declaration

The first row of the sixteen bindings said "use site," and its proof was indirect: it showed
that satisfaction is not written in the source, not where it is established. This section
gives the direct proof: it deliberately breaks conformance and shows exactly where the
compiler stops. The tool is the `compiles` function from the Go Fundamentals course's
`unwritten.go` frame — it writes a piece of source into a temporary package, calls the
compiler, and reads only whether it passed.

```go
// compiler.go — tests from inside the program whether a piece of source compiles
package main

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

func compiles(source string) bool {
	dir, err := os.MkdirTemp("", "trial")
	if err != nil {
		return false
	}
	defer os.RemoveAll(dir)
	os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module trial\n\ngo 1.24\n"), 0o644)
	os.WriteFile(filepath.Join(dir, "code.go"), []byte(source), 0o644)
	cmd := exec.Command("go", "build", "./...")
	cmd.Dir = dir
	cmd.Env = append(os.Environ(), "GOTOOLCHAIN=local")
	return cmd.Run() == nil
}
```

These two pieces of source carry the same half-implementation — a type writes only one of a
two-method interface's methods. The difference is a single line: the second carries a
conformance proof right after the type.

```go
// conformance.go — the compiler stays silent with no proof, stops with a proof in the declaration
package main

import "fmt"

func main() {
	sourceA := `package trial

type FullInterface interface {
	First() string
	Second() string
}

type HalfImplementation struct{}

func (h HalfImplementation) First() string { return "first" }
`
	sourceB := sourceA + `
var _ FullInterface = HalfImplementation{}
`
	fmt.Println("no proof, never used at all    :", compiles(sourceA))
	fmt.Println("with proof, right after the type:", compiles(sourceB))
}
```

```
no proof, never used at all    : true
with proof, right after the type: false
```

`HalfImplementation` is identical in both sources — the `Second` method is missing. In the
first source this type is never used anywhere as `FullInterface`; the compiler stays silent
and the missing method produces no warning at all. In the second source, the only line added
is a conformance proof placed right after the type's definition; the same gap now stops
compilation. The binding moved into the **declaration** in the second source — the proof line
writes into the source not the satisfaction itself, but the **verification** of that
satisfaction, and when that verification breaks, compilation stops right there.

**IN3.** This measurement has a blind spot: `compiles` only reads `cmd.Run()`'s exit status,
never the error text. Making sure the first source really compiles for **no other** reason
than the missing method was set up by hand — the source was written to carry no problem beyond
`Second`. The same discipline holds everywhere in the course a measurement calls the compiler.

A type left without a conformance proof never shows its gap as long as it is never used. This
is the real cost of saying "established at the use site": the binding is only tested the
moment it is used, and never tested if it is not. A proof line closes this risk by moving it
next to the type's own definition.

#### Summary

- A type satisfying an interface is implicit: no word declares this in the source; the only
  condition is that the method signatures match.
- Of sixteen bindings, **4 are established in a declaration, 4 at a use site, 8 in the
  value** — the course's binding split was produced in this lesson.
- The `File` type satisfies four interfaces (`Writer`, `Closer`, `Embedded`, `Short`) and its
  own source has zero references to any of the four.
- The small-interface principle is a measurement, not advice: as method count climbs from one
  to two to three, the number of the same four types able to satisfy it falls from three to
  two to one.
- A compile-time conformance proof (`var _ Interface = Type{}`) moves the binding into the
  declaration; without the proof, a missing implementation never shows itself as long as it is
  not used.

#### Next Step

In this lesson an interface value's dynamic type appeared a few times — `main.File`, read with
`%T` — but was never dwelt on. The next lesson grows exactly this line: the empty interface,
wanting no method at all, accepts whatever type is placed in it, but the placed type's
information is never lost. Where does that information go, and how does `%T` read it back?

### The Empty Interface and `any`

Source: https://academia.sh/en/courses/go-type-system/empty-interface-and-any

The empty interface wants zero methods and every type satisfies zero methods: which type will arrive is never written in the source, and type information is carried inside the value. When the same value is carried by three separate static types, the dynamic type does not change; the cost is that anything placed in the value turns into a pair and cannot be called directly. Limiting measurement: when a nil pointer is placed in the empty interface, the interface itself is not nil.

The previous lesson read an interface value's dynamic type once — `main.File`, with `%T` —
and did not dwell on it. The empty interface is exactly that line grown large: an interface
wanting no method at all, defined not by a count of members but by the number zero. Since the
source never writes which type will be placed in this interface, someone somewhere has to
carry the placed type's information; the previous lesson's binding split already named that
place — **value**. This lesson grows that line.

Interfaces like the previous lesson's `Writer` and `Closer` wanted one method each; `Embedded`
wanted both together. The empty interface stands at the far end of that scale: it wants no
method at all. The relationship between how little an interface asks for and how many types
can satisfy it was measured with a count in the previous lesson; this lesson focuses on that
curve's zero point and measures what is gained and lost there.

#### An Interface Wanting Zero Methods

In the previous lesson, as method count climbed from one to two to three, the number of the
same four types able to satisfy it fell from three to two to one. The empty interface is the
zero end of that same curve: `interface{}` wants no method at all, and a condition with no
methods is satisfied by every type — an empty set is a subset of every set. The language's own
type name is `any`, an alias for `interface{}`; this lesson calls the concept the **empty
interface**, its name in code `any`.

Because the empty interface wants no method, the source places no limit at all on which type
can go into it: `var v any = 42`, `var v any = "text"`, `var v any = Unit{}` all three compile,
because all three have zero methods to satisfy. But an interface wanting zero methods does not
mean the placed value keeps no information at all. Type information is not lost, only its
visibility changes — it stops being a type name readable from the source and turns into a
piece of information that has to be asked for at runtime.

Every `fmt.Println` call written throughout this lesson is already using this same mechanism:
the standard library's function takes its parameters as `...any`. Whether given an integer, a
string, or a struct, each is placed into the empty interface at the moment of the call and read
back inside `Println` with a query similar to `%T`. The empty interface is therefore not a
rare edge case — every output line in this lesson has already passed through it.

#### The Same Value, Three Static Types

A value's dynamic type is independent of which static type carries that value. The following
measurement passes the same `Unit` value through three separate static types — its own
interface, then the empty interface, then the empty interface directly — reading the dynamic
type each time with `%T`.

```go
// same-value.go — the same value carried through three static types, the dynamic type does not change
package main

import "fmt"

type Unit struct{ Name string }

func (u Unit) Identity() string { return u.Name }

type Identifier interface{ Identity() string }

func main() {
	u := Unit{Name: "unit-42"}

	var t1 Identifier = u
	var t2 any = t1
	var t3 any = u

	fmt.Println("as Identifier         :", fmt.Sprintf("%T", t1))
	fmt.Println("after moving to any    :", fmt.Sprintf("%T", t2))
	fmt.Println("directly as any        :", fmt.Sprintf("%T", t3))
}
```

```
as Identifier         : main.Unit
after moving to any    : main.Unit
directly as any        : main.Unit
```

Three variables carry three separate static types — `Identifier`, then `any`, then `any` again
but arriving by a different path — and all three give the same answer: `main.Unit`. The move
from `t1` to `t2` is not a conversion, it is the same pair (type, value) binding to a new
variable once more; type information is not lost because there was never anything to lose at
any point — the interface value already carried this pair from the very start. `Identifier`'s
single method and the empty interface's zero methods both display the same underlying
structure; only the visible method count differs.

**IN4.** `t3 any = u` was written instead of `t3 := u` in order to also show a path where
`Unit` is placed directly into the empty interface — a third line was deliberately added to
tell apart the `t1` and `t2` chain, which passes through `Identifier`, from `t3`, which does
not.

#### The Same Container, Four Separate Dynamic Types

The previous measurement passed a single value through three separate static types and showed
the dynamic type staying fixed. This measurement does the reverse: it places four separate
dynamic types into a single static type (`[]any`) and shows each keeping its own identity.

```go
// mixed.go — the same any slice carries four different dynamic types side by side
package main

import "fmt"

type Unit struct{ Name string }

func main() {
	values := []any{42, "text", Unit{Name: "x"}, 3.14}
	for _, v := range values {
		fmt.Printf("value=%v dynamic type=%T\n", v, v)
	}
	fmt.Println("slice's static type: []any, length:", len(values))
}
```

```
value=42 dynamic type=int
value=text dynamic type=string
value={x} dynamic type=main.Unit
value=3.14 dynamic type=float64
slice's static type: []any, length: 4
```

**IN5.** The array was deliberately chosen from four separate basic categories — an integer, a
string, a struct, a floating-point number — so that the demonstration is visible as the empty
interface's general rule and not a behavior specific to a single type family (numeric types
alone, for instance).

The slice itself carries a single static type: `[]any`. But its four elements carry four
separate dynamic types — `int`, `string`, `main.Unit`, `float64` — and the `range` loop never
mixes any of them up, because each element brings its own pair along with it. This mirrors the
previous measurement: there the same type stayed fixed across three static forms, here the
same static form carries four separate types. Together the two make clear where type
information really sits — not in the static type, but in the pair each value carries on its
own.

A heterogeneous container like this — holding values of different types in a single slice —
could only be built with the empty interface before generic data structures written with type
parameters existed. Where the two ways of writing this diverge will be measured in the course's
generics section; what matters here is only how the empty interface pulls this off: by storing
each element together with its own pair.

#### The Cost: Being a Pair, Not Callable Directly

Carrying type information inside the value comes with a cost: a value placed in the empty
interface cannot be called **directly** through its own methods. Since `any` wants zero
methods, it offers zero methods — no method is defined on it, even if the placed value has
methods of its own. Calling it first requires returning to the real type (or an interface the
real type satisfies) with a type assertion.

This is the symmetric counterpart of the previous lesson's small-interface measurement. There,
as method count grew, the number of types able to satisfy it fell; here, when method count
drops to zero, access also drops to zero. The empty interface accepts every type because it
wants nothing, and because it wants nothing it also offers nothing — acceptance breadth and
direct access are inversely related. The compiler makes no guess here: as long as `v`'s static
type is `any`, it has no way of knowing which methods exist on `v`, because that information
can only be learned at runtime by looking at the value itself.

```go
// compiler.go — tests from inside the program whether a piece of source compiles
package main

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

func compiles(source string) bool {
	dir, err := os.MkdirTemp("", "trial")
	if err != nil {
		return false
	}
	defer os.RemoveAll(dir)
	os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module trial\n\ngo 1.24\n"), 0o644)
	os.WriteFile(filepath.Join(dir, "code.go"), []byte(source), 0o644)
	cmd := exec.Command("go", "build", "./...")
	cmd.Dir = dir
	cmd.Env = append(os.Environ(), "GOTOOLCHAIN=local")
	return cmd.Run() == nil
}
```

This block is only written to disk; the real measurement, producing two pieces of source and
calling the compiler, follows below.

```go
// cost.go — a direct call over any is rejected, a call after a type assertion is valid
package main

import "fmt"

func main() {
	common := `package trial

type Unit struct{ Name string }

func (u Unit) Identity() string { return u.Name }

type Identifier interface{ Identity() string }

func F() string {
	var v any = Unit{Name: "x"}
`
	directCall := common + `	return v.Identity()
}
`
	assertedCall := common + `	return v.(Identifier).Identity()
}
`
	fmt.Println("direct call, over any        :", compiles(directCall))
	fmt.Println("call after a type assertion  :", compiles(assertedCall))
}
```

```
direct call, over any        : false
call after a type assertion  : true
```

`v.Identity()` does not compile, because `v`'s static type is `any` and `any`'s method set is
empty; the compiler does not even search for a method named `Identity` on `any`, because there
is no method defined there to search for. `v.(Identifier).Identity()` compiles, because the
type assertion first converts the value to `Identifier`, and a call can be made through
`Identifier`'s (and therefore `Identity`'s) method set. The cost is this: `any` accepts a
value from every type, but in exchange it closes off direct access to that value's own
behavior. The type assertion itself is the next lesson's topic; here only its **necessity** is
measured.

This rejection is not a gap, it is a deliberate boundary. When the compiler looks at the line
`v.Identity()`, all it has is `v`'s static type (`any`); which concrete value will sit there is
not yet known at compile time, it is determined only the first time it runs. The compiler does
not fill this gap by guessing "`Identity` is probably there" — such a guess would only blow up
at runtime, possibly in production, if it turned out wrong. Instead, the compiler never steps
outside the method set the static type offers; since that set is empty for the empty
interface, the call stops right there. The type assertion is the only way to close this gap
**explicitly**.

#### Limiting Measurement: An Interface Carrying a Nil Pointer Is Not Nil

The strangest line involving the empty interface shows that carrying type information does not
come free. A pointer can be `nil`; when that same pointer is placed into the empty interface,
the interface itself does **not** become nil.

```go
// nil-interface.go — even if the pointer is nil, the interface carrying it is not nil
package main

import "fmt"

type Server struct{}

func main() {
	var s *Server
	var v1 any = s
	var v2 any

	fmt.Println("is the pointer itself nil     :", s == nil)
	fmt.Println("is v1 carrying the pointer nil:", v1 == nil)
	fmt.Println("is v2, given no value at all, nil:", v2 == nil)
}
```

```
is the pointer itself nil     : true
is v1 carrying the pointer nil: false
is v2, given no value at all, nil: true
```

The three lines were placed side by side deliberately. `s` really is `nil` — it points to no
`Server` instance. `v1` carries this `nil` pointer, but `v1 == nil` gives `false`: the
interface value is a pair, and this pair's type slot is filled — `*main.Server` sits there —
only the value slot is `nil`. The pair only counts as `nil` when **both** of its slots are
empty; `v2` never received any value, so both its type and value slots stay empty, and it
really is `nil`.

The type slot of the pair carrying genuinely separate information becomes even clearer when two
different pointer types are placed side by side.

```go
// two-nils.go — the nil values of two separate pointer types are not counted equal
package main

import "fmt"

type A struct{}
type B struct{}

func main() {
	var a *A
	var b *B
	var v1 any = a
	var v2 any = b

	fmt.Println("is v1 nil          :", v1 == nil)
	fmt.Println("is v2 nil          :", v2 == nil)
	fmt.Println("is v1 equal to v2  :", v1 == v2)
	fmt.Printf("v1 dynamic type=%T, v2 dynamic type=%T\n", v1, v2)
}
```

```
is v1 nil          : false
is v2 nil          : false
is v1 equal to v2  : false
v1 dynamic type=*main.A, v2 dynamic type=*main.B
```

`a` and `b` are both `nil` pointers and both, on the surface, point to a "empty" struct with no
fields. But `v1` and `v2` are not equal to each other, because an interface comparison first
compares the type slot, and `*main.A` and `*main.B` are not the same type. Even though both
value slots are `nil`, the two are not counted equal as pairs because their type slots differ.
This is one more proof that the type slot carries real information independent of the value
slot — not just "is it empty or not," it shows the "which type" question also enters the
comparison.

**IN6.** This is the direct consequence of the previous lesson's first claim: if eight of the
bindings are carried inside the value, a value being empty does not mean the pair is empty.
The Go Fundamentals course's Zero Values lesson measured that `nil` can be appended to a slice
but cannot be written to a `nil` map; this line is a third example from the same family — the
word `nil` stays the same, the meaning it carries changes from type to type.

The practical consequence of this line is this: if a function returns `error` (itself an
interface) and internally uses a concrete pointer type, the returned error value may **not** be
`nil` even if that pointer is `nil`. The caller asking `err == nil` gets `false`, because what
it is asking about is not the pointer itself but the pair carrying it. This trap's full
measurement will be done in the lesson devoted to error values; here only its origin is shown.

There is a reason for seeing this origin so early: an error is itself an interface, and
everything that is an interface is bound to the same pair rule. `error` is not the empty
interface — it wants the `Error() string` method — but what it shares with the empty interface
is exactly this lesson's subject: both are a pair carrying a type slot and a value slot. In the
empty interface this pair is easy to observe directly, because no method stands in the way; in
the error interface the same pair hides behind a method signature, but its behavior does not
change.

#### Summary

- The empty interface wants zero methods and every type satisfies zero methods; `any` is this
  interface's name in code.
- When the same value is carried by three separate static types (its own interface, then
  `any`, then directly as `any`), the dynamic type does not change — type information is not
  lost, only its visibility changes.
- A value placed in the empty interface cannot be called directly; since `any`'s method set is
  empty, a call first requires a type assertion.
- When a nil pointer is placed in the empty interface, the interface itself is not nil — the
  pair is only counted nil when both its type and value slots are empty together.
- The `nil` values of two separate pointer types are not counted equal when placed in the
  empty interface; the comparison looks at the type slot before the value slot.

#### Next Step

In this lesson, type information was accessed only with `%T`, for reading purposes alone — no
line ever recovered `v`'s real value, only read which type it was. But the question a program
actually needs to ask is usually not "what is this value's type" but "does this value satisfy
this type," and if it does, "give me the real value." This was already seen once on the way
out of the empty interface: `v.(Identifier)` recovered the value but without checking the
answer. The next lesson measures these two questions separately: a type assertion's two-value
form and the branch a type switch selects — two separate ways of asking the value.

### Type Assertion and Type Switch

Source: https://academia.sh/en/courses/go-type-system/type-assertion-and-type-switch

Two of five values satisfy an interface; the type assertion's two-value form counts this without panicking, the single-value form panics when it fails, and the panic is only read as panic=true. A type switch distributes the same five values into its own branches. Limiting measurement: the same value can fit both a concrete-type branch and an interface branch, and the branch chosen depends on writing order — the lesson runs this with two orderings.

The previous lesson measured that the empty interface carries a value's type information
without losing it, but it only looked at that information with `%T`, for reading purposes.
The real use of that information is not to read it but to ask it: does this value satisfy this
interface, and if it does, give me the real value. Go asks this question with two separate
constructs — a **type assertion** asks about a single candidate, a **type switch** picks among
several candidates. Both ask the value, both look inside the same pair (type, value).

What both are asking is the most repeated pattern among the sixteen bindings in the first
lesson: if a binding is not written in the source and the compiler does not establish it at
the point of use, only one path remains — asking the value itself at runtime. This lesson
measures the two forms of that asking, their results, and the risk one of them carries.

#### The Type Assertion's Two-Value Form

A type assertion is written as `value.(Type)` and has two forms. The single-value form
(`v := value.(Type)`) returns only the value; if the assertion is wrong, it produces a runtime
error. The two-value form (`v, ok := value.(Type)`) returns a second boolean value; if the
assertion is wrong, `ok` becomes `false` and the program does not stop.

What is written in place of `Type` can ask two separate questions. When a concrete type is
written ("is this value's dynamic type exactly this"), the comparison looks at the dynamic
type itself. When an interface is written ("does this value satisfy this contract"), the
comparison looks at the method set. Both share the same `value.(Type)` syntax; which question
is being asked is understood only from `Type`'s own nature — whether it is concrete or an
interface. The following measurement asks an interface question: it tests five separate values
against the same interface and counts how many satisfy it.

```go
// five-questions.go — how many of five values satisfy the Storer interface
package main

import "fmt"

type Storer interface{ Store() string }

type DiskRecord struct{}

func (DiskRecord) Store() string { return "disk" }

type MemoryRecord struct{}

func (MemoryRecord) Store() string { return "memory" }

type Temporary struct{}

func main() {
	values := []any{DiskRecord{}, 42, MemoryRecord{}, "text", Temporary{}}
	trueCount := 0
	for i, v := range values {
		store, ok := v.(Storer)
		if ok {
			fmt.Printf("value#%d dynamic_type=%T Storer_ok=%t call=%s\n", i, v, ok, store.Store())
			trueCount++
			continue
		}
		fmt.Printf("value#%d dynamic_type=%T Storer_ok=%t\n", i, v, ok)
	}
	fmt.Println("true count out of five questions:", trueCount)
}
```

```
value#0 dynamic_type=main.DiskRecord Storer_ok=true call=disk
value#1 dynamic_type=int Storer_ok=false
value#2 dynamic_type=main.MemoryRecord Storer_ok=true call=memory
value#3 dynamic_type=string Storer_ok=false
value#4 dynamic_type=main.Temporary Storer_ok=false
true count out of five questions: 2
```

Two of five values (`DiskRecord`, `MemoryRecord`) satisfy `Storer`, three (`int`, `string`,
`Temporary`) do not. `Temporary` is a struct and still does not satisfy it, because it never
wrote the `Store` method — the condition for interface satisfaction is not being a struct, it
is the method set matching. The `ok` variable gives a definite answer to each of these five
questions, and the loop never stops at any point; this is the two-value form's real strength.

The `store` variable carries the second half of this measurement. A type assertion does not
only answer "does it satisfy" — when `ok` is true, `store` becomes a real value whose static
type is now `Storer`, and its `Store` method is callable. The `call=disk` and `call=memory`
columns show that this value is genuinely usable — the "cannot be called directly" cost from
the previous lesson is fully repaid here: once a type assertion succeeds, the value reopens the
access the empty interface had closed off.

**IN7.** Five values were deliberately chosen from three separate failure reasons: `int` and
`string` carry no method at all, `Temporary` is a struct but has the wrong (empty) method set.
All three give the same `false` answer but for different reasons — the measurement does not
tell this difference apart, it only counts whether each satisfies or not.

When the two-value form fails, the first variable (`store`) is not something random, it is the
target type's own zero value — since `Storer` is an interface here, its zero value is `nil`.
This confirms once more a point tied together by the previous two lessons: in Go, what stands
in for something unwritten or failed is never left undefined, the zero value of its own type
always steps in.

#### When the Single-Value Form Fails: Panic

The two-value form turns a failure into a value; the single-value form turns the same failure
into a panic. The following measurement runs this directly.

```go
// panic-measurement.go — a single-value type assertion panics when it fails
package main

import "fmt"

type Storer interface{ Store() string }

type DiskRecord struct{}

func (DiskRecord) Store() string { return "disk" }

func panics(f func()) (p bool) {
	defer func() {
		if recover() != nil {
			p = true
		}
	}()
	f()
	return false
}

func main() {
	var successful any = DiskRecord{}
	var unsuccessful any = 42

	fmt.Println("does a successful single-value assertion panic:", panics(func() {
		_ = successful.(Storer)
	}))
	fmt.Println("does an unsuccessful single-value assertion panic:", panics(func() {
		_ = unsuccessful.(Storer)
	}))
	fmt.Println("does an unsuccessful two-value assertion panic:", panics(func() {
		_, ok := unsuccessful.(Storer)
		_ = ok
	}))
}
```

```
does a successful single-value assertion panic: false
does an unsuccessful single-value assertion panic: true
does an unsuccessful two-value assertion panic: false
```

All three lines test the same `unsuccessful` value (an `int`); only the call form changes. The
single-value form does not panic for `DiskRecord{}`, because the assertion really is correct.
The same form panics for `42`, because `int` does not satisfy `Storer` and the single-value
form throws a runtime error in this case — `panics` catches this error with `recover` and reads
it only as `panic=true`, no stack trace is ever printed. The third line tries the same failing
assertion with the two-value form, and no panic happens at all; `ok` quietly becomes `false`.
The difference is in the form: the same question, two separate result contracts.

**IN8.** `panics` is rebuilt here with the same function from the Go Fundamentals course's
`unwritten.go` frame: it runs a function and catches with `recover` whether it panicked,
returning only one of two values. This is the single method used everywhere in the course that
measures a panic.

This two-value example alone is not enough: if all five values are run through the
single-value form, the number that panics should come out equal to the `false` count from the
earlier section — the two are reading the same fact from two separate contracts.

```go
// panic-count.go — how many of five values panic in the single-value form
package main

import "fmt"

type Storer interface{ Store() string }

type DiskRecord struct{}

func (DiskRecord) Store() string { return "disk" }

type MemoryRecord struct{}

func (MemoryRecord) Store() string { return "memory" }

type Temporary struct{}

func panics(f func()) (p bool) {
	defer func() {
		if recover() != nil {
			p = true
		}
	}()
	f()
	return false
}

func main() {
	values := []any{DiskRecord{}, 42, MemoryRecord{}, "text", Temporary{}}
	panicCount := 0
	for i, v := range values {
		panicked := panics(func() {
			_ = v.(Storer)
		})
		fmt.Printf("value#%d dynamic_type=%T panicked=%t\n", i, v, panicked)
		if panicked {
			panicCount++
		}
	}
	fmt.Println("panicking count out of five values:", panicCount)
}
```

```
value#0 dynamic_type=main.DiskRecord panicked=false
value#1 dynamic_type=int panicked=true
value#2 dynamic_type=main.MemoryRecord panicked=false
value#3 dynamic_type=string panicked=true
value#4 dynamic_type=main.Temporary panicked=true
panicking count out of five values: 3
```

The panicking count is three, the exact complement of the earlier measurement's `trueCount`
(two) — two of the five values satisfied it, three did not, and now those same three panic in
the single-value form. Two measurements ask the same five values, against the same interface,
with two separate call forms, and produce two numbers that add up to five. This is proof that
the two forms carry the same information: the difference is not in the information, it is in
how the failure is reported.

#### The Branch a Type Switch Selects

A type switch selects a single branch among several candidates. The `switch v.(type)` syntax
tries each `case` in order and continues from the first one that matches. The no-fall-through
rule measured in the Go Fundamentals course's Control Flow lesson holds here too: the matching
branch does not fall into the next `case`, the branch ends on its own unless `fallthrough` is
written. The same five values now pass through a four-branch type switch.

```go
// pick-branch.go — a type switch distributes five values into its own branches
package main

import "fmt"

type Storer interface{ Store() string }

type DiskRecord struct{}

func (DiskRecord) Store() string { return "disk" }

type MemoryRecord struct{}

func (MemoryRecord) Store() string { return "memory" }

type Temporary struct{}

func pickBranch(v any) string {
	switch t := v.(type) {
	case int:
		return fmt.Sprintf("integer branch: %d", t)
	case string:
		return fmt.Sprintf("string branch: %q", t)
	case DiskRecord:
		return "DiskRecord branch"
	case MemoryRecord:
		return "MemoryRecord branch"
	default:
		return "default branch"
	}
}

func main() {
	values := []any{DiskRecord{}, 42, MemoryRecord{}, "text", Temporary{}}
	for i, v := range values {
		fmt.Printf("value#%d -> %s\n", i, pickBranch(v))
	}
}
```

```
value#0 -> DiskRecord branch
value#1 -> integer branch: 42
value#2 -> MemoryRecord branch
value#3 -> string branch: "text"
value#4 -> default branch
```

Four of the five values fall into a branch dedicated to their own concrete type; `Temporary`
matches no `case` and falls to `default`. The `t` in `t := v.(type)` is a variable whose static
type inside each branch is specific to that branch — in the `case int` branch `t` is an `int`,
in the `case string` branch a `string`. This is the type switch's difference from a type
assertion: a type assertion asks about a single type, a type switch runs the same query against
several candidates at once and reports which one matched.

The same result could also be reached with five separate type assertions — writing an
`if d, ok := v.(X); ok` line for each candidate and picking the first true one. The type
switch gathers this into a single construct and automatically supplies `t`'s branch-specific
static type; with five separate assertions, this type would have to be rewritten by hand in
every branch. What is gained is not a new capability, it is the same query gathered in one
place.

#### Limiting Measurement: The Branch Chosen Depends on Writing Order

A type switch accepts not only concrete types but also interfaces as branches. If a value fits
both a concrete-type branch and an interface branch, which one is chosen depends on the
**writing order** of the branches — the first branch that matches wins.

The four branches in the previous section (`int`, `string`, `DiskRecord`, `MemoryRecord`)
excluded each other: a single value could fit only one of these branches. But a concrete type
and an interface it satisfies do not exclude each other — `DiskRecord` means both `DiskRecord`
itself and `Storer`. When two `case`s both claim the same value, a type switch does not raise a
conflict error, it silently picks whichever was written first.

```go
// order-dependency.go — the same value gives two different results depending on branch order
package main

import "fmt"

type Storer interface{ Store() string }

type DiskRecord struct{}

func (DiskRecord) Store() string { return "disk" }

func classifyConcreteFirst(v any) string {
	switch v.(type) {
	case DiskRecord:
		return "concrete: DiskRecord"
	case Storer:
		return "interface: Storer"
	default:
		return "unknown"
	}
}

func classifyInterfaceFirst(v any) string {
	switch v.(type) {
	case Storer:
		return "interface: Storer"
	case DiskRecord:
		return "concrete: DiskRecord"
	default:
		return "unknown"
	}
}

func main() {
	v := DiskRecord{}
	fmt.Println("if the concrete branch is written first  :", classifyConcreteFirst(v))
	fmt.Println("if the interface branch is written first :", classifyInterfaceFirst(v))
}
```

```
if the concrete branch is written first  : concrete: DiskRecord
if the interface branch is written first : interface: Storer
```

`DiskRecord{}` fits both branches: its own concrete type and `Storer`, because it satisfies the
`Store` method. `classifyConcreteFirst` selects the `DiskRecord` branch because it is written
first; `classifyInterfaceFirst` drops the same value into the `Storer` branch, only because the
branches' order was reversed. Both functions carry exactly the same two `case`s in their
source, only their order has changed; the result is entirely different. This looks at the same
fact the first lesson's small-interface measurement did, from a different angle: a value can
satisfy more than one interface at the same time, and a type switch resolves that plurality by
selecting whichever branch was written first.

**IN9.** This order dependency holds not only between a concrete-type branch and an interface
branch, but also between two separate interface branches — if a value satisfies two interfaces
at once, whichever interface branch was written first still wins. The measurement here tests
only a single pair (concrete type, interface) because this is the form most commonly
encountered in real sources; the principle stays the same for more than two branches too.

The consequence of this contradicts the usual intuition about an ordinary `switch`: in a plain
`switch`, branch order generally only affects readability, not the result, because each branch
compares against a separate, mutually exclusive value. In a type switch, branches may not
exclude each other — a value can fit more than one branch at the same time — and in that case
order is no longer a mere readability preference, it is the decision that determines which
branch runs. What the measurement shows is exactly this: two functions carry the same two
`case`s, take the same value, and produce two separate answers purely because one line moved.

#### Summary

- A type assertion's two-value form (`v, ok := value.(Type)`) turns a failure into a boolean
  value; two of five values satisfy `Storer`, three do not.
- The single-value form (`v := value.(Type)`) panics when it fails; the panic is only caught
  with `recover` and read as `panic=true`/`panic=false`.
- A type switch runs the same query against several candidates at once and continues from the
  first matching branch, never falling into the next one.
- A value can fit both a concrete-type branch and an interface branch; which branch is chosen
  depends on the source's writing order, not on the value itself.
- The two-value form producing no panic, and the single-value form's panic count (three)
  exactly matching the two-value form's `false` count (three), shows that both call forms carry
  the same information, only reporting failure differently.
- Once a type assertion succeeds, the returned value is genuinely callable; the "cannot be
  called directly" cost from the previous lesson is repaid after a successful assertion.

#### Next Step

In this lesson, `Storer` wanted a single method and established a single contract. But when a
type has to satisfy more than one contract at the same time — able to both store and be closed
— a way is needed to combine these two contracts in the source. The `Storer` branch in the
limiting measurement was itself a hint of such a combination: `DiskRecord` only wrote `Store`
but could have satisfied other contracts too. The next lesson measures this combination: how do
embedded interfaces establish a contract in the source, and how does this combination happen
without adding a single new method to the implementation?

### Interface Embedding

Source: https://academia.sh/en/courses/go-type-system/interface-embedding

Embedding combines two interfaces into a single composite contract in the source, and this combination adds no new method at all: the same contract, built once by embedding and once rewritten by hand, wants the same two methods. The minimum method count a type must write to satisfy the composite interface equals the total method count of the embedded interfaces, no more, no fewer. Limiting measurement: embedding only combines the contract, not the implementation — two separate types can each satisfy their own separate contract, yet neither satisfies the composite contract.

The previous lesson saw a type able to satisfy more than one contract at the same time, but
each contract stood separately in the source. When a type needs to both be able to store and
be closable, a way is needed to refer to these two requirements with a single name. Embedding
provides that way: an interface can take in other interfaces into its own body just by writing
their names. This is unlike any of the bindings in the previous three lessons — explicitly
written in the source, and so the clearest example of the **declaration** class.

Of the sixteen bindings in the first lesson, only four were in a declaration, and two of those
were already about embedding: `Embedded` itself and the compile-time proof that verified it.
This lesson grows those two lines and measures embedding's own rules — what it combines, how
much it combines, and where it stops. Three measurements proceed in this order: first it is
confirmed that embedding really adds no new method; then how many methods are needed at
minimum to satisfy the composite contract is found; finally, where this combination stops —
in the contract or in the implementation — is tested.

#### Combining Contracts in the Source

An interface embedding is nothing more than writing another interface's name inside an
interface body. The result is a new interface wanting the full set of methods from the
embedded interfaces. The syntax is short: no field name is placed in front of the embedded
interface, only its own name is written as a single line — the same writing rule embedding
borrows from struct embedding, a line that looks like an unnamed field but is actually a type
name. The following measurement builds the same contract two ways — once by embedding, once by
rewriting it by hand — and counts the method count both want with `reflect`.

```go
// equivalence.go — a contract built with embedding asks for the same method count as one written by hand
package main

import (
	"fmt"
	"reflect"
)

type Reader interface{ Read() string }
type Writer interface{ Write(m string) }

// Embedding: two interface names are written into the embedding interface's body.
type ReadWriterEmbedded interface {
	Reader
	Writer
}

// The same contract, rewritten by hand.
type ReadWriterManual interface {
	Read() string
	Write(m string)
}

func main() {
	a := reflect.TypeOf((*ReadWriterEmbedded)(nil)).Elem()
	b := reflect.TypeOf((*ReadWriterManual)(nil)).Elem()

	fmt.Println("method count wanted by embedding  :", a.NumMethod())
	fmt.Println("method count wanted by hand-writing:", b.NumMethod())
	for i := 0; i < a.NumMethod(); i++ {
		fmt.Println("  method:", a.Method(i).Name)
	}
}
```

```
method count wanted by embedding  : 2
method count wanted by hand-writing: 2
  method: Read
  method: Write
```

`ReadWriterEmbedded`'s source carries only two names (`Reader`, `Writer`), and writes no
method signature at all. But when `reflect` is asked, it reports wanting exactly two methods,
`Read` and `Write`, just like `ReadWriterManual`, which writes both methods by hand. Embedding
is a shortcut — it lets you write less in the source — but the contract it produces is
**identical, byte for byte**, to the one written by hand. This is an extension of the embedding
concept from the Go Fundamentals course's Structs lesson: there a struct carried another
struct's field and method names without writing them; here an interface carries another
interface's method names without writing them. Both are called by the same word
(**embedding**) because both use the same mechanism: writing one name brings everything that
name carries along with it.

**IN10.** The syntax `reflect.TypeOf((*T)(nil)).Elem()` is the standard way to obtain an
interface type itself (not an interface *value*) as a `reflect.Type` — `(*T)(nil)` produces a
pointer value, `.Elem()` descends to the interface type that pointer points to. No real `T`
value is ever constructed throughout this measurement; only the type itself is queried.

Embedding is not limited to two interfaces here; an embedded interface can itself embed
another interface, and the chain can stretch several levels. The following measurement goes one
level deeper by embedding `ReadWriter` with a third interface (`Closer`) and tests that the
total method count is still summed correctly.

```go
// chain.go — two-level embedding, the total method count still adds up correctly
package main

import (
	"fmt"
	"reflect"
)

type Reader interface{ Read() string }
type Writer interface{ Write(m string) }
type Closer interface{ Close() error }

type ReadWriter interface {
	Reader
	Writer
}

type FullSource interface {
	ReadWriter
	Closer
}

func main() {
	t := reflect.TypeOf((*FullSource)(nil)).Elem()
	fmt.Println("two-level embedding, total method count:", t.NumMethod())
	for i := 0; i < t.NumMethod(); i++ {
		fmt.Println("  method:", t.Method(i).Name)
	}
}
```

```
two-level embedding, total method count: 3
  method: Close
  method: Read
  method: Write
```

`FullSource` embeds `ReadWriter`, and `ReadWriter` itself embeds `Reader` and `Writer` — two
levels. The total method count is again three, the sum of the three individually embedded
methods (`Read`, `Write`, `Close`). Adding more levels does not change the summing rule: each
level carries every name below it upward, no name is ever lost, and no name is ever counted
twice.

This clarifies what someone reading `FullSource` needs to see: the source carries only two
lines (`ReadWriter`, `Closer`), yet the contract wants three methods. Reaching this count from
the source is not possible without opening the intermediate level (`ReadWriter`) — the reader
has to follow the whole chain. Embedding shortens the source, but it does not reduce the
reading burden by the same amount; a short source can still carry a long contract.

#### How Many Methods Have to Be Written

Since embedding adds no method, the minimum method count a type must write to satisfy a
composite interface should be the sum of the embedded interfaces' methods — no more, no fewer.
`reflect` counted this by asking an interface **type**; the following measurement asks the same
question by testing a **type against an interface definition**, taken straight from the
compiler itself. Both should arrive at the same number: the count `reflect` reports is the
smallest count the compiler accepts.

```go
// compiler.go — tests from inside the program whether a piece of source compiles
package main

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

func compiles(source string) bool {
	dir, err := os.MkdirTemp("", "trial")
	if err != nil {
		return false
	}
	defer os.RemoveAll(dir)
	os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module trial\n\ngo 1.24\n"), 0o644)
	os.WriteFile(filepath.Join(dir, "code.go"), []byte(source), 0o644)
	cmd := exec.Command("go", "build", "./...")
	cmd.Dir = dir
	cmd.Env = append(os.Environ(), "GOTOOLCHAIN=local")
	return cmd.Run() == nil
}
```

```go
// how-many-methods.go — the minimum method count needed to satisfy a composite interface is measured
package main

import "fmt"

func main() {
	common := `package trial

type Reader interface{ Read() string }
type Writer interface{ Write(m string) }

type ReadWriter interface {
	Reader
	Writer
}

`
	zeroMethods := common + `type Candidate struct{}

var _ ReadWriter = Candidate{}
`
	oneMethod := common + `type Candidate struct{}
func (Candidate) Read() string { return "" }

var _ ReadWriter = Candidate{}
`
	twoMethods := common + `type Candidate struct{}
func (Candidate) Read() string { return "" }
func (Candidate) Write(m string) {}

var _ ReadWriter = Candidate{}
`
	fmt.Println("does it compile with zero methods:", compiles(zeroMethods))
	fmt.Println("does it compile with one method   :", compiles(oneMethod))
	fmt.Println("does it compile with two methods  :", compiles(twoMethods))
}
```

```
does it compile with zero methods: false
does it compile with one method   : false
does it compile with two methods  : true
```

The zero-method candidate does not compile, because both are missing. The one-method candidate
(only `Read`) does not compile either, because `Write` is still missing — satisfying `Reader`
does not mean satisfying `ReadWriter`; embedding does not let one interface stand in for the
other, it wants both together. The two-method candidate compiles, and this is exactly the
number `reflect` counted in the previous section. Three candidates test a single threshold (two
methods) from both directions: one short is not enough, the exact count is enough.

This threshold follows the same axis as the small-interface measurement in the third lesson:
the more methods an interface wants, the heavier the load on the type that satisfies it.
Embedding does not **reduce** the method count here; it only makes it easier to refer to two
separate contracts with a single name. The cost of satisfying `ReadWriter` is the sum of the
costs of satisfying `Reader` and `Writer` separately — embedding offers no shortcut that lowers
this sum, it only gathers the naming into one place.

**IN11.** All three candidates carry the same name (`Candidate`) and are tested against the
same `ReadWriter`; the only thing that changes is `Candidate`'s own method set. This makes the
three measurements comparable — the difference sits in a single variable, everything else stays
fixed. The same discipline could also be tested with a fourth candidate — one writing three
methods, two matching the names `ReadWriter` wants and a third an extra method. A type like
this would still compile, because interface satisfaction is a **subset** relationship: as long
as a type's method set covers what the interface wants, having more is not a problem.

#### Limiting Measurement: The Contract Combines, the Implementation Does Not

Embedding combines two contracts in the source, but this combination gives no
**implementation** guarantee. A type satisfying the composite interface also satisfies the two
embedded interfaces separately — that direction is easy. But the reverse is not true: two
separate types can each satisfy two separate contracts separately, and neither satisfies the
composite contract. The threshold measurement in the previous section asked "how many methods
are enough" over a single type; this measurement asks the same question over more than one
type, and shows there is no sharing at all between types.

```go
// two-directions.go — the two directions of satisfying a composite contract are tested separately
package main

import "fmt"

type Reader interface{ Read() string }
type Writer interface{ Write(m string) }

type ReadWriter interface {
	Reader
	Writer
}

type File struct{}

func (File) Read() string  { return "content" }
func (File) Write(m string) {}

type ReadOnly struct{}

func (ReadOnly) Read() string { return "a" }

type WriteOnly struct{}

func (WriteOnly) Write(m string) {}

func main() {
	var f any = File{}
	_, reads := f.(Reader)
	_, writes := f.(Writer)
	_, composite := f.(ReadWriter)
	fmt.Println("forward direction, is File a Reader     :", reads)
	fmt.Println("forward direction, is File a Writer     :", writes)
	fmt.Println("forward direction, is File a ReadWriter :", composite)

	var reader any = ReadOnly{}
	var writer any = WriteOnly{}
	_, readerOk := reader.(Reader)
	_, writerOk := writer.(Writer)
	_, readerCompositeOk := reader.(ReadWriter)
	_, writerCompositeOk := writer.(ReadWriter)
	fmt.Println("reverse direction, is ReadOnly a Reader        :", readerOk)
	fmt.Println("reverse direction, is WriteOnly a Writer       :", writerOk)
	fmt.Println("reverse direction, is ReadOnly a ReadWriter    :", readerCompositeOk)
	fmt.Println("reverse direction, is WriteOnly a ReadWriter   :", writerCompositeOk)
}
```

```
forward direction, is File a Reader     : true
forward direction, is File a Writer     : true
forward direction, is File a ReadWriter : true
reverse direction, is ReadOnly a Reader        : true
reverse direction, is WriteOnly a Writer       : true
reverse direction, is ReadOnly a ReadWriter    : false
reverse direction, is WriteOnly a ReadWriter   : false
```

In the forward direction, a single type (`File`) satisfies all three interfaces — a type
satisfying the composite contract automatically also satisfies its parts, because the
composite contract's method set is a superset of the parts. `File` having written its `Read`
and `Write` methods is enough on its own; it does not need to write different code three times
to separately satisfy `Reader`, `Writer`, and `ReadWriter`.

The reverse direction is different: `ReadOnly` satisfies only `Reader`, `WriteOnly` only
`Writer`, and both correctly. But **neither** satisfies `ReadWriter` — not `ReadOnly`, not
`WriteOnly`. The two contracts being satisfied separately does not mean the composite contract
is also satisfied; that requires a single type to write **both** methods together. Even if
both `ReadOnly` and `WriteOnly` exist in the program, a place wanting `ReadWriter` accepts
neither of them — there is no such thing as their "sum" in the type system.

**IN12.** `ReadOnly` and `WriteOnly` together, "somewhere," satisfying both `Read` and `Write`
carries no meaning anywhere in the program — Go's type system offers no mechanism that
automatically merges two separate types' methods. Embedding only combines the **interface
definition**; a third type wanting to combine two separate concrete types' behavior has to
either write its own methods or take both concrete types into itself through struct embedding,
as in the K01 Structs lesson.

This distinction explains the basic difference between struct embedding and interface
embedding. When a struct embeds another struct, the embedded type's real value — with its own
fields and methods — sits concretely inside the outer struct; that was the reason the K01
Structs lesson's embedded methods could be called without being written on the outer type. When
an interface embeds another interface, there is no value at all involved, only a list of
signatures is merged. Interface embedding carries no value, it only carries a request; who
satisfies that request is a separate question, and the answer to that question can only be
given by a single concrete type's own methods (or that type's own struct embeddings). Both
kinds of embedding share the same word because both rest on the same idea — writing a name
and gaining everything it carries — but one carries a value while the other carries only a
contract.

#### Summary

- Interface embedding is nothing more than writing other interfaces' names into an interface's
  body; the binding stands explicitly in the source, the clearest example of the declaration
  class in this course.
- A contract built with embedding wants, at the `reflect` level, exactly the same method count
  as a contract rewriting the same methods by hand — embedding adds no new method.
- The minimum method count a type must write to satisfy a composite interface equals the sum of
  the embedded interfaces': one short is not enough, the exact count is enough.
- A type satisfying the composite interface also satisfies each of the embedded interfaces, but
  not the reverse: two separate types can satisfy two separate contracts separately while
  neither satisfies the composite contract.
- Interface embedding carries no value, only a contract; struct embedding (the K01 Structs
  lesson) carries a real value — the same word, two separate mechanisms.

#### Next Step

In this lesson, the party defining the contract acted **before** the type that would satisfy
it: `Reader` and `Writer` were already there before `File` was written. But the third lesson's
`Storer`, and the first lesson's `Short`, never appeared in the source of the type that
satisfied them — the party defining the contract was the party consuming it. The difference
between these two patterns is not an accident, it is a choice: sometimes the producing party
writes the contract (this lesson), sometimes the consuming party does (the first and third
lessons). The next lesson turns this choice into a design decision: who should define an
interface, the producing package or the consuming side, and does this have a measurable
counterpart?

### Interface Design

Source: https://academia.sh/en/courses/go-type-system/interface-design

Consumer-side definition is measured: a four-method type is written without referencing any interface at all, four separate consumers unaware of each other each define their own single-method interface, and the same type satisfies all four — the producer's source has zero references to any of these four names. Limiting measurement: when the same contract is defined under two separate names in two consumers, they appear unable to stand in for each other, yet the type system accepts both directly against the same value.

The previous lesson opened with an example where the party defining the contract acted
**before** the type that would satisfy it: `Reader` and `Writer` were already there before
`File` was written. But the first lesson's `Short` and the third lesson's `Storer` did the
opposite — neither ever appeared in the source of the type that satisfied it. This lesson
turns that observation into a design decision: who should define an interface, the producing
side or the consuming side, and does this choice have a measurable counterpart?

This question is not a preference, it is the natural extension of the first lesson's second
claim. If the source never says a type satisfies an interface, that interface does not
**need to exist** in the producing side's source at all — the consumer can write a contract
the producer never knew about and apply it to the same type afterward. This lesson shows that
possibility once more, this time at scale and with a cost attached: how many separate
contracts can a single type be wrapped in by how many independent consumers, and what
invisibility appears in exchange for that flexibility.

#### Consumer-Side Definition

The following measurement writes a four-method type (`Store`) without referencing any
interface at all. Then four separate consumers, unaware of each other, each define their own
single-method interface. The measurement counts how many of these four interfaces `Store`
satisfies.

```go
// store.go — the producer side's code is written without referencing any interface at all
package main

import "fmt"

type Store struct{ items []string }

func (s *Store) Add(o string)     { s.items = append(s.items, o) }
func (s *Store) Remove(o string) bool { return true }
func (s *Store) List() []string   { return s.items }
func (s *Store) Size() int        { return len(s.items) }

// Four separate consumers' own interfaces, each unaware of the others.
type Writer interface{ Add(o string) }
type Cleaner interface{ Remove(o string) bool }
type Reporter interface{ List() []string }
type Measurable interface{ Size() int }
type Backer interface{ Backup() error }

func main() {
	var s any = &Store{}
	_, isWriter := s.(Writer)
	_, isCleaner := s.(Cleaner)
	_, isReporter := s.(Reporter)
	_, isMeasurable := s.(Measurable)
	_, isBacker := s.(Backer)

	count := 0
	for _, ok := range []bool{isWriter, isCleaner, isReporter, isMeasurable, isBacker} {
		if ok {
			count++
		}
	}
	fmt.Println("is Writer     :", isWriter)
	fmt.Println("is Cleaner    :", isCleaner)
	fmt.Println("is Reporter   :", isReporter)
	fmt.Println("is Measurable :", isMeasurable)
	fmt.Println("is Backer     :", isBacker)
	fmt.Println("count satisfied out of five candidates:", count)
}
```

```
is Writer     : true
is Cleaner    : true
is Reporter   : true
is Measurable : true
is Backer     : false
count satisfied out of five candidates: 4
```

`Store`'s source is four lines: `Add`, `Remove`, `List`, `Size`. None of the names `Writer`,
`Cleaner`, `Reporter`, `Measurable` appear in these four lines — these four interfaces might
not even have existed yet when `Store` was written. Despite this, `Store` satisfies all four,
because the only condition for satisfaction is the method signatures matching. The fifth
candidate (`Backer`) was added deliberately: it wants a method (`Backup`) `Store` never wrote,
and returns `false` — the measurement shows not only what fits but also what does not, so the
result is a real boundary and not a cherry-picked sample.

Each of the four interfaces represents a different consumer scenario: `Writer` is the need of a
piece of code that only adds new items, `Cleaner` of a function that only removes, `Reporter`
of a view that only lists, `Measurable` of a check that only tests size. These four consumers
may never have seen each other, may have been written in separate packages by separate teams —
the only thing they have in common is `Store` itself. Yet each can still build its own narrow
contract and fit `Store` to it, because conformance checking is done from each consumer's own
point of view rather than a central registry.

**IN13.** The four consumer interfaces each want a single method; this is a deliberate choice.
The first lesson's small-interface measurement showed that as method count grows, the number of
satisfying types falls. Here the reverse is used: reducing method count to one nearly
guarantees `Store` satisfies every consumer with a single method — this is one reason
consumer-side definition works this often in practice.

In this arrangement the producing package is written once and never changes; each new consumer
defines its own interface for its own need. In the course's terminology, this is the same
mechanism that moves the binding of interface satisfaction to the **use site** — exactly what
the first lesson's `Short` and the third lesson's `Storer` did. The difference is that this
lesson pulls the same idea out of a single example and turns it into a design observation: four
consumers, four different needs, four different names, and the producing side knows about none
of them. The Design Principles course's interface-segregation principle presents this pattern
as advice; this lesson does not give advice, it counts the pattern.

The count itself also says something: `Store` satisfies four interfaces, yet **zero**, not
four, interface names appear in `Store`'s own source. With `Store`'s source never changing, a
new interface wanting any subset of `Store`'s existing four methods can be defined at any time,
and `Store` keeps satisfying it too — the same rule holds for a fifth, sixth, tenth consumer,
and the producing side's source never needs to know about it. Whoever writes `Store` does not
need to know how many consumers wrap it in how many different contracts.

#### The Consumer's Own Composite

The fourth lesson measured that embedding combines contracts on the **producing side**:
`Reader` and `Writer` were already combined inside `ReadWriter` before `File` was written. The
same combination is possible on the consumer side too — only this time the combining party is
not the producer, it is the consumer itself.

```go
// consumer-composite.go — the consumer gathers the producer type's four methods into its own contract
package main

import "fmt"

type Store struct{ items []string }

func (s *Store) Add(o string)         { s.items = append(s.items, o) }
func (s *Store) Remove(o string) bool { return true }
func (s *Store) List() []string       { return s.items }
func (s *Store) Size() int            { return len(s.items) }

type Writer interface{ Add(o string) }

// A fifth consumer gathers the four methods into its own composite contract; it does not use
// embedding, it writes all four signatures by hand.
type FullStore interface {
	Add(o string)
	Remove(o string) bool
	List() []string
	Size() int
}

func main() {
	var s any = &Store{}
	_, isWriter := s.(Writer)
	_, isFullStore := s.(FullStore)
	fmt.Println("narrow contract,  is Writer  :", isWriter)
	fmt.Println("wide contract, is FullStore  :", isFullStore)
}
```

```
narrow contract,  is Writer  : true
wide contract, is FullStore  : true
```

`FullStore` gathers all four of `Store`'s methods into a single contract, but not by embedding
— it writes all four by hand. `FullStore` is not itself an embedding chain. Despite this,
`Store` satisfies it too, exactly as it satisfies the single-method `Writer`. The fourth
lesson's measurement showed how embedding combines a contract on the **producing side**; this
measurement shows the same combination can also be built on the **consumer side**, with the
producer never knowing about it. The two models do not clash here, they stand side by side on
the same type: `Store` satisfies both the narrowest contract (`Writer`) and the widest one
(`FullStore`) at the same time, because both want different subsets of the same four
methods.

**IN14.** `FullStore` wanting exactly the four methods `Store` really writes is a deliberate
choice — had the contract gone beyond the producer's method set, `Store` would not have
satisfied it. What the measurement shows is not unlimited conformance: no matter how wide a
contract a consumer writes, it cannot go beyond what the producer actually wrote.

This clarifies the real boundary of consumer-side definition: the consumer cannot choose
**what** the producer writes, it can only choose **which** of the producer's writings it wants.
Had `FullStore` wanted a fifth method (`Backup`), it would not have been satisfied, just like
`Backer` above — writing a wide contract does not substitute for adding a new method to the
producing side's source. The freedom consumer-side definition gives is over the **selection**
of existing methods, not over the **invention** of methods that do not exist.

#### Limiting Measurement: The Same Contract, Two Separate Names

Consumer-side definition does not come free. When two separate consumers define two separate
interfaces wanting the same method, these two interfaces appear to share no common point in
the source at all — both stand in their own package, under their own name, unaware of each
other. But Go's type system compares interfaces **by method set, not by name**; this is why two
separate names, as long as they carry the same signature, stand in for each other.

```go
// two-names.go — two separate interface names carrying the same signature stand in for each other
package main

import "fmt"

type Store struct{ items []string }

func (s *Store) Add(o string) { s.items = append(s.items, o) }

// Two consumers, unaware of each other, define two interfaces with the same signature.
type Writer interface{ Add(o string) }
type Input interface{ Add(o string) }

func firstConsumer(w Writer) string  { w.Add("A"); return "added through Writer" }
func secondConsumer(g Input) string  { g.Add("B"); return "added through Input" }

func main() {
	s := &Store{}

	var g Input = s
	var w Writer = g // can a value of type Input be assigned directly to a Writer variable
	fmt.Println("was the Input value assignable to a Writer variable:", w != nil)

	fmt.Println(firstConsumer(g)) // an Input value passes to a Writer parameter
	fmt.Println(secondConsumer(w)) // a Writer value passes to an Input parameter
	fmt.Println("total items added:", len(s.items))
}
```

```
was the Input value assignable to a Writer variable: true
added through Writer
added through Input
total items added: 2
```

`Writer` and `Input` are two separate interfaces, written unaware of each other, carrying two
separate names. There is no embedding, no shared definition between them at all. Despite this,
the line `var w Writer = g` compiles — an `Input` value can be assigned directly to a `Writer`
variable — and `firstConsumer` accepts an `Input` value while expecting a `Writer`. The
difference is only in the name; Go's type system defines interfaces not by their names but by
the methods they want. When two separate consumers define two separate interfaces wanting the
same method, these two definitions unintentionally become **equivalent**.

**IN15.** This line cannot be predicted just by reading the source: someone looking at the
`Writer` and `Input` definitions one at a time cannot see that a relationship exists between
them, because that relationship is written nowhere — it only surfaces when the two method sets
are compared. The measurement shows this blind spot by running it directly; reading the source
is not enough, the two definitions have to be placed side by side and their signatures
compared.

The boundary of this equivalence is also clear: the signatures have to be **identical**, mere
resemblance is not enough. The following measurement asks this directly of the compiler.

```go
// boundary.go — the same signature makes it valid, a different return type rejects it
package main

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

func compiles(source string) bool {
	dir, err := os.MkdirTemp("", "trial")
	if err != nil {
		return false
	}
	defer os.RemoveAll(dir)
	os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module trial\n\ngo 1.24\n"), 0o644)
	os.WriteFile(filepath.Join(dir, "code.go"), []byte(source), 0o644)
	cmd := exec.Command("go", "build", "./...")
	cmd.Dir = dir
	cmd.Env = append(os.Environ(), "GOTOOLCHAIN=local")
	return cmd.Run() == nil
}

func main() {
	common := `package trial

type Writer interface{ Add(o string) }

func f(w Writer) {}

`
	sameSignature := common + `type Input interface{ Add(o string) }

func g(x Input) { f(x) }
`
	differentSignature := common + `type Transmitter interface{ Add(o string) error }

func g(x Transmitter) { f(x) }
`
	fmt.Println("same-signature interface valid      :", compiles(sameSignature))
	fmt.Println("different-signature interface valid :", compiles(differentSignature))
}
```

```
same-signature interface valid      : true
different-signature interface valid : false
```

`Input` carries exactly the same signature (`Add(string)`) as `Writer`, so it is accepted
where `Writer` is expected. `Transmitter`, on the other hand, carries the same name (`Add`)
but wants a different return type (`error`), and this difference alone is enough to break the
equivalence — the "does it compile" question turns to `false`. Equivalence looks not at the
name but at **every part** of the signature: method name, parameter types, return types, all
of them have to match together. Two interfaces independently written by two consumers only
become implicitly equivalent when both repeat the exact same contract word for word; even a
small difference breaks this implicit binding.

This is the cost of consumer-side definition: when independent consumers write independent
contracts, nothing anywhere prevents the same contract from being repeated more than once,
unaware of itself. The result is not wrong — the two interfaces really are equivalent, and the
type system handles this correctly — but it is an **invisible** equivalence to someone reading
the source. The same freedom that is useful when each of the four interfaces builds the
narrowest contract for its own consumer turns into an extra contract when two consumers
independently define the same need separately.

#### Summary

- In consumer-side definition, the producing side produces a type without writing any
  interface at all, and the consuming side defines its own interface according to its own
  need.
- `Store` satisfies all four consumer interfaces, and its source has zero references to any of
  the four names; a fifth candidate (`Backer`) goes unsatisfied and shows the boundary.
- Small, single-method consumer interfaces make this pattern easier in practice — the lower
  the method count, the higher the odds of being satisfied.
- Go's interface comparison looks at the method set, not the name; this is why two separately
  named interfaces carrying the same signature stand in for each other even with no connection
  written in the source.
- This is the cost of consumer-side definition: independent consumers can unknowingly repeat
  the same contract, and this repetition only becomes visible once the signatures are compared.
- The boundary of equivalence is exact: even if the method name is the same, if the parameter
  or return type differs, the two interfaces do not stand in for each other; the compiler
  rejects this difference directly.

#### Next Step

The consumer defining the interface is the natural consequence of a fact measured throughout
this course: if the binding is never written in the source at all, who writes the contract is
also not bound to the producing side's decision. The frame measured across five lessons
(declaration, use site, value) held here too: embedding sits in the declaration, consumer-side
definition sits at the use site, and both are two different faces of the same implicit
satisfaction principle. The next topic looks at where this model is used the most. In Go, an
error is an **interface**, and every error value satisfies it implicitly — not a three-method
or a four-method contract, but the narrowest possible contract with a single method. The next
lesson's question: where does an error value's identity sit, and what is it that makes an
error an error?

## Error Handling

Error being a value and implicitly satisfying the interface, sentinel errors carrying their identity in their value, wrapping preserving the chain while adding context and unwrapping searching the chain for the target, and in which case panic and recovery are legitimate and in which they are misuse.

### The Error Interface

Source: https://academia.sh/en/courses/go-type-system/error-interface

An error is an ordinary value in Go and implicitly satisfies the error interface: any type that writes Error() string becomes an error without ever saying so in its source. Three types confirm this, their dynamic types read with %T; a value-receiver error's structural equality is shown apart from a pointer-receiver error's identity equality. The limiting measure: when a nil pointer is placed into the error interface, the interface itself is not nil.

The previous topic's last lesson measured that a consumer can define an interface the
producing side never writes in its source at all — a natural consequence of the binding
never being written in the source. This lesson looks at where that same model is used most in
Go: the standard library itself is built around a single interface, `error`, and every error
value satisfies it in exactly the same way, without writing any declaration.

`error` is an ordinary return value in Go and an ordinary interface — it has a single method:
`Error() string`. This lesson's question is the same one carried over from the previous
topic: where is the binding that satisfies this interface established? To find the answer,
three separate types will be returned as errors, with the word `error` never appearing in any
of their source.

The Functions lesson (M09/K01) already measured `error` as an ordinary return value and
showed that a caller ignoring it without ever catching it is rejected in no way — that
observation is not rebuilt here. This lesson looks at **what** that return value is, and when
a type counts as "an error." The question asked will stay the same for the rest of the
course: is a binding written explicitly in the source, established at the use site the
compiler looks at, or carried only inside the value at run time?

This topic advances this question in four steps. This lesson measures why an error is a
value and how it satisfies the interface; the next lesson measures where an error's identity
stands, the third measures how context added to an error is preserved through a chain, and the
fourth measures where the binding of an exit path never visible in the signature — panic — is
established (or not).

#### Three Types, Zero Declarations

The previous topic measured implicit satisfaction on a single type in the Interface Concept
lesson; this lesson applies the same mechanism to error values. The three types below were
written independently of each other: one a value-receiver struct, one a pointer-receiver
struct, one a named type built on an integer. What all three have in common is a single line:
a method matching the `Error() string` signature.

Choosing three types in deliberately different forms is not arbitrary. The `error` interface
is used so widely in the standard library that almost every type shape — value, pointer,
named base type, even an empty struct — becomes able to satisfy it. The measurement's goal is
to show that this diversity does not affect satisfaction at all: the interface wants a single
method, and does not care which type carries that method or how.

```go
// binding.go — is the error binding established at the use site, or inside the value
package main

import "fmt"

var useSite, value int

func observe(name, location, evidence string) {
	switch location {
	case "use":
		useSite++
	default:
		value++
	}
	fmt.Printf("%-46s %-9s %s\n", name, location, evidence)
}

func summary() {
	fmt.Printf("binding: %d use site, %d value\n", useSite, value)
}
```

```go
// measurement.go — an error is a value: three types write Error() string without ever mentioning it, and become errors
package main

import "fmt"

type FormatError struct{ Field string }

func (b FormatError) Error() string { return "invalid format: " + b.Field }

type AccessError struct{ Code int }

func (e *AccessError) Error() string { return fmt.Sprintf("access denied: code %d", e.Code) }

type CounterError int

func (s CounterError) Error() string { return fmt.Sprintf("counter overflow: %d", int(s)) }

func main() {
	var h1 error = FormatError{Field: "name"}
	var h2 error = &AccessError{Code: 403}
	var h3 error = CounterError(9)

	observe("three types satisfying the error interface", "use",
		"the word 'error' never appears in their source")
	observe("error value's dynamic type", "value", fmt.Sprintf("%T, %T, %T", h1, h2, h3))

	var a error = FormatError{Field: "name"}
	var b error = FormatError{Field: "name"}
	observe("equality of two value-receiver errors", "value", fmt.Sprint(a == b))

	var c error = &AccessError{Code: 403}
	var d error = &AccessError{Code: 403}
	observe("equality of two pointer-receiver errors", "value", fmt.Sprint(c == d))

	summary()
}
```

```
three types satisfying the error interface     use       the word 'error' never appears in their source
error value's dynamic type                     value     main.FormatError, *main.AccessError, main.CounterError
equality of two value-receiver errors          value     true
equality of two pointer-receiver errors        value     false
binding: 1 use site, 3 value
```

**EH1.** None of the three types ever mentions the word `error` in its source; `FormatError`,
`AccessError` and `CounterError` only write a method matching the `Error() string` signature,
and this is enough to make them errors. The binding is established at the line
`var h1 error = FormatError{...}`, the moment the value is **placed** into an `error`
variable — nowhere in the source does it say "this type satisfies the error interface." The
dynamic type read with `%T` confirms that all three actually complete satisfaction: the value
type appears as `main.FormatError`, the pointer type as `*main.AccessError`, the named type as
`main.CounterError`.

**EH2.** The last two lines produce and compare two independent copies from the same
`FormatError` value, and two independent pointers from the same `AccessError` value. A
value-receiver error is compared **structurally**: two separate variables come out equal
because their fields are the same. A pointer-receiver error, on the other hand, is compared by
**identity**: two separate `&AccessError{...}` calls produce two separate addresses, and
equality returns `false`, even when their fields are exactly the same. This difference is not
arbitrary — the next lesson builds a package-level error value's identity exactly on this
distinction.

These two lines also carry a small warning: the `error` interface supporting the comparison
operator (`==`) does not mean two errors are "the same error" — it only means the pair the
interface carries (dynamic type, dynamic value) is equal on both sides. For a value-receiver
error, this reduces to the equality of the fields; if even one field differs, the comparison
returns `false`. This distinction joins, in the section below, the question of which type's
method set the same method enters.

#### Method Set: It Is the Type That Satisfies, Not the Value

The Go Fundamentals course's Pointers lesson measured that whether a method has a value
receiver or a pointer receiver comes from the receiver's definition, not from the call
notation — that mechanic is not rebuilt here. But for this lesson's question, one single
consequence matters: when `AccessError`'s `Error` method is written with a pointer receiver,
which type actually satisfies the `error` interface — `AccessError` itself, or only
`*AccessError`? Asking the compiler for the answer requires borrowing K01's `compiles` tool: a
piece of source is written to a temporary package, compiled, and only whether it passed is
read.

This tool only reads whether it passed, it never reads the error text — what is measured is
not "why was it rejected," it is "was it rejected." Both pieces of source share the same
`AccessError` definition and the same empty function body; the only difference between them is
whether the value placed into the `error` variable carries a `.` mark. This also shows how
small a distinction interface satisfaction can rest on, at the level of syntax.

```go
// methodset.go — a pointer-receiver method only puts the pointer type into the error interface
package main

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

func compiles(source string) bool {
	dir, err := os.MkdirTemp("", "trial")
	if err != nil {
		return false
	}
	defer os.RemoveAll(dir)
	os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module trial\n\ngo 1.24\n"), 0o644)
	os.WriteFile(filepath.Join(dir, "code.go"), []byte(source), 0o644)
	cmd := exec.Command("go", "build", "./...")
	cmd.Dir = dir
	cmd.Env = append(os.Environ(), "GOTOOLCHAIN=local")
	return cmd.Run() == nil
}

const types = `
package trial
type AccessError struct{ Code int }
func (e *AccessError) Error() string { return "access denied" }
`

func main() {
	valueAssignment := types + "func F() { var _ error = AccessError{Code: 1} }"
	pointerAssignment := types + "func F() { var _ error = &AccessError{Code: 1} }"

	observe("assigning a value type to an error variable", "use", fmt.Sprint(compiles(valueAssignment)))
	observe("assigning a pointer type to an error variable", "use", fmt.Sprint(compiles(pointerAssignment)))
	summary()
}
```

```
assigning a value type to an error variable    use       false
assigning a pointer type to an error variable  use       true
binding: 2 use site, 0 value
```

**EH3.** The `AccessError` value stops compilation when it tries to be assigned to `error`;
the `*AccessError` pointer passes the same line without a problem. Both carry the same
fields, both can have the same `Code` value — the distinction is not in the data itself, it is
in which type has the `Error() string` method. When a method is written with a pointer
receiver, that method enters only the pointer type's **method set**; the value type's method
set never contains it.

**EH4.** This clarifies even further where implicit satisfaction is established: the binding
looks not only at "is there an `Error() string` method," but at "does this **exact type's**
method set have this method." The compiler checks this at every `var _ error = ...` line, at
the **use site** where the assignment is made — even though a declaration like
`AccessError implements error` never appears anywhere in the source, the check is real and
produces two different results.

This distinction has a practical consequence: writing a type's error method with a pointer
receiver means only that type's pointer form will count as an error. If a function tries to
return the value `AccessError{...}` directly as `error`, compilation stops right there; the
function needs to return `&AccessError{...}`. This choice is not arbitrary — a design that
wants to prevent a large struct from being copied on every return has, at the same time, also
chosen that only that struct's pointer form enters the error interface.

#### Limiting Measure: A Full Interface Does Not Look nil

What a path that does not return an error returns is also an `error` value — usually `nil`.
But where this `nil` comes from matters. The `validate` function below returns a concrete
pointer type (`*FieldError`), and when the field is filled in, this pointer really is `nil`.
The problem starts on the line where this concrete value is placed into an `error`
variable — and this line looks as ordinary as the `check` function's return statement.

This example combines two observations measured in the previous two sections into a single
line: `FieldError`'s `Error` method has a pointer receiver, so only `*FieldError` satisfies
`error` — just like `AccessError`. And an interface value is a pair carrying the dynamic type
and the dynamic value together — just like the three types measured in the first section. The
difference this time is that the dynamic value itself is `nil`, and this does not affect the
interface as a whole.

```go
// trap.go — a nil-pointer concrete error is no longer nil once placed into the error interface
package main

import "fmt"

type FieldError struct{ Field string }

func (a *FieldError) Error() string { return "invalid field: " + a.Field }

func validate(field string) *FieldError {
	if field == "" {
		return &FieldError{Field: "required"}
	}
	return nil
}

func check(field string) error {
	return validate(field)
}

func main() {
	concrete := validate("name")
	err := check("name")

	observe("the concrete pointer itself", "value", fmt.Sprint(concrete == nil))
	observe("error interface carrying the same value", "value", fmt.Sprint(err == nil))
	observe("error interface's dynamic type", "value", fmt.Sprintf("%T", err))
	summary()
}
```

```
the concrete pointer itself                    value     true
error interface carrying the same value        value     false
error interface's dynamic type                 value     *main.FieldError
binding: 0 use site, 3 value
```

**EH5.** The call `validate("name")` really returns a `nil` pointer — the third line confirms
this. But the moment `check` **returns** this pointer in a variable of type `error`, the
interface value becomes two-part: the dynamic type (`*FieldError`) and the dynamic value
(`nil` pointer). The comparison `err == nil` looks at whether the interface **itself** is
empty; because the dynamic type is filled in, the interface is counted as full too, even
though the pointer it carries is `nil`.

**EH6.** This is the topic's common measure's strangest counterpart among error values: eight
of the sixteen bindings were carried inside the value, and this line shows exactly why those
eight are not free. The place the binding breaks is neither compilation nor the moment of the
call — it surfaces only when `== nil` is asked of the value. The practical consequence is
clear: if a function is going to return `error`, its signature should use the `error`
interface directly, not a concrete pointer type; otherwise, a `nil` return from a function
like `validate` can turn into a full error in the caller's hands.

The insidiousness of this trap is not in the error staying **hidden**, it is quite the
opposite: the check `err != nil` works perfectly honestly and gives the correct answer — the
interface really is full, because it carries the dynamic type. What is mistaken is the
expectation of the person who wrote the code: assuming that in every case `validate` returns
`nil`, `check` will also return `nil`. This assumption comes from thinking that the pointer
itself and the interface carrying the pointer are the same thing; but as the third line
shows, the two answer different questions.

The four observations measured throughout this lesson settle into a single frame. The
implicit satisfaction binding is never written in the source, and two different mechanisms
fill its place: the method-set match the compiler checks at an assignment line (use site), and
the dynamic type and dynamic value the interface value carries within itself (value). These
two are not independent of each other — the binding established at the use site determines
**what** the pair later carried inside the value will carry. The `nil` trap is born exactly in
this transition: once the match is established, the interface is full from then on, even if
the carried value itself is `nil`.

#### Summary

- `error` is an ordinary single-method interface (`Error() string`); a type satisfying it is
  written nowhere in the source, it is established only by the method's presence.
- A value-receiver error is compared structurally (the value is equal if the fields are
  equal); a pointer-receiver error is compared by identity (the value is not equal if the
  addresses are not equal).
- The dynamic type read with `%T` shows which concrete type an error value satisfies the
  interface with; this information stands not in the source, but in the value itself.
- Even when a concrete pointer type is `nil`, once that pointer is placed into an `error`
  variable, the interface value is counted as full — the comparison returns `false` because
  the dynamic type is carried.
- This behavior is the reason functions that will return `error` should use the `error`
  interface directly in their signature, not a concrete type.
- The binding is established in two places, in neither as a declaration: method-set matching
  is the use site the compiler checks at an assignment line, and the pair of dynamic type and
  dynamic value is the value itself.

#### Next Step

The two errors measured in this lesson (`FormatError`, `AccessError`) were each written
individually and reproduced on every call; neither was shared as a single package-level value.
But when a caller wants to **recognize** a specific error type — wants to ask "is this exactly
that error?" — it needs to rely on the error's identity, not its text. The comparison
difference between value-receiver and pointer-receiver errors measured in this lesson
prepares exactly the ground for this question: making an identity reliable requires the
comparison to be identity-based, not structural. Just as this lesson measured that the type
satisfying the `error` interface never mentions the word `error`, the next lesson measures
where this identity stands: why can an error value defined once at the package level still be
told apart from another error carrying the same text?

### Sentinel Errors

Source: https://academia.sh/en/courses/go-type-system/sentinel-errors

An error value defined once at the package level has its identity standing not in its text but in itself: another errors.New value carrying the same text does not come out equal when compared with it, because both are two different values of type *errors.errorString. errors.Is gives the same result as ==. The limiting measure: when a sentinel error's identity changes, the caller silently falls into the wrong branch, and no warning is produced.

The previous lesson measured that a value-receiver error is compared structurally, a
pointer-receiver error by identity. This lesson carries that difference one step further:
when a caller wants to **recognize** a specific error, what does it rely on? The answer rests
on a common pattern in Go — the **sentinel error**: an exported error value defined once at
the package level. This lesson's question is: where does this value's identity stand, and why
is it not confused with another error carrying the same text?

This name comes from the same family as the **sentinel variable** in the Algorithms course
and the **sentinel value** in the Python course: there, a single value marking the end of an
array or a special position; here, a single value marking a failure **state**. The concept is
not rebuilt; what is shared is only the idea — a special value that reduces a search or check
loop to a single comparison, instead of writing a separate rule at every step.

#### Same Text, Separate Identity

`errors.New` produces a new value every time it is called. The `lookupFirst` function below
returns the package-level `ErrNotFound` sentinel; `lookupSecond`, on the other hand, returns a
value carrying the same text but produced **separately** with `errors.New`. Both look as
though they are telling the user the same thing.

These two usually come from two separate packages in real code: a storage layer defines a
sentinel like `ErrNotFound`; the caller compares against this sentinel to handle the "record
did not exist" state specially. The problem starts when another layer — perhaps a third
package wanting to carry the same meaning — writes its own call to
`errors.New("record not found")`; even though the text looks the same to a reader, the two
values carry two separate identities, unaware of each other.

```go
// binding.go — is the error binding established at the use site, or inside the value
package main

import "fmt"

var useSite, value int

func observe(name, location, evidence string) {
	switch location {
	case "use":
		useSite++
	default:
		value++
	}
	fmt.Printf("%-49s %-9s %s\n", name, location, evidence)
}

func summary() {
	fmt.Printf("binding: %d use site, %d value\n", useSite, value)
}
```

```go
// sentinel.go — an error value defined at the package level is set apart by identity from another value with the same text
package main

import (
	"errors"
	"fmt"
)

var ErrNotFound = errors.New("record not found")

func lookupFirst(id int) error {
	if id != 1 {
		return ErrNotFound
	}
	return nil
}

func lookupSecond(id int) error {
	if id != 1 {
		return errors.New("record not found")
	}
	return nil
}

func main() {
	h1 := lookupFirst(9)
	h2 := lookupSecond(9)

	observe("dynamic type of two values", "value", fmt.Sprintf("%T, %T", h1, h2))
	observe("== comparison with the sentinel", "value", fmt.Sprint(h1 == ErrNotFound))
	observe("errors.Is comparison with the sentinel", "value", fmt.Sprint(errors.Is(h1, ErrNotFound)))
	observe("== result for a different same-text value", "value", fmt.Sprint(h2 == ErrNotFound))
	observe("errors.Is result for a different same-text value", "value", fmt.Sprint(errors.Is(h2, ErrNotFound)))
	observe("text comparison of two different values", "value", fmt.Sprint(h1.Error() == h2.Error()))
	summary()
}
```

```
dynamic type of two values                        value     *errors.errorString, *errors.errorString
== comparison with the sentinel                   value     true
errors.Is comparison with the sentinel            value     true
== result for a different same-text value         value     false
errors.Is result for a different same-text value  value     false
text comparison of two different values           value     true
binding: 0 use site, 6 value
```

**EH7.** The first line repeats the previous lesson's dynamic-type measurement here and gives
a surprising result: `h1` and `h2` carry the **same** dynamic type, `*errors.errorString` — in
the standard library, `errors.New` returns a pointer to this one type, no matter what text you
give it. So being the same type does not mean being the same value. `h1 == ErrNotFound`
returns `true` because `lookupFirst` really returned the same package-level variable — the
same pointer carrying the same address. `h2 == ErrNotFound`, on the other hand, returns
`false`, because `lookupSecond` produced a **new value** with its own `errors.New` call; even
though the text is exactly the same, this means a different struct standing at a different
memory location.

**EH8.** `errors.Is(h1, ErrNotFound)` and `errors.Is(h2, ErrNotFound)` give exactly the same
result as `==` on these two unwrapped errors. The reason is `errors.Is`'s behavior: if the
target error is not wrapped, the comparison falls back directly to `==`. This equivalence
holds only in this lesson — the next lesson measures why `==` becomes insufficient once an
error is wrapped, and why `errors.Is` becomes necessary. The last line, in turn, shows that
text comparison (`Error() == Error()`) answers a question entirely separate from identity
comparison: two different values return `true` because their text is exactly the same — a
shortcut that would give the wrong answer to the identity question.

This measurement also explains why the sentinel error pattern is built with `var`, not
`const`. Because `error` is an interface, it cannot be bound to a constant — constants can
only be defined on basic types (number, string, bool). A sentinel error behaving like a
"constant" does not come from a language restriction, it comes from the entire package
accessing, with the same pointer, a single variable created once at the package level and
never changed again. Preserving identity depends on this single variable being **shared**; if
every caller produces its own `errors.New` copy, identity comparison loses its meaning from
the very start.

#### Custom Identity: Comparison Established With the Is Method

The behavior of `errors.Is` measured in this lesson — falling back to `==` on an unwrapped
error — is not its only rule. The `errors` package gives an error the option of handing
comparison off to a method it defines itself: a type writing `Is(target error) bool`, when
called by `errors.Is`, answers according to its own logic, not pointer equality. This shows
that identity does not always mean "the same address" — a type can write its own definition
of identity.

```go
// codeerror.go — an identity established with a custom Is method: identity can be defined without pointer equality
package main

import (
	"errors"
	"fmt"
)

type CodeError struct{ Code int }

func (k *CodeError) Error() string { return fmt.Sprintf("error code %d", k.Code) }

func (k *CodeError) Is(target error) bool {
	h, ok := target.(*CodeError)
	return ok && h.Code == k.Code
}

func main() {
	a := &CodeError{Code: 404}
	b := &CodeError{Code: 404}
	c := &CodeError{Code: 500}

	observe("== result for two separate pointers", "value", fmt.Sprint(a == b))
	observe("comparison of the same code via the Is method", "value", fmt.Sprint(errors.Is(a, b)))
	observe("comparison of a different code via the Is method", "value", fmt.Sprint(errors.Is(a, c)))
	summary()
}
```

```
== result for two separate pointers               value     false
comparison of the same code via the Is method     value     true
comparison of a different code via the Is method  value     false
binding: 0 use site, 3 value
```

**EH9.** `a` and `b` are two separate pointers of type `CodeError`; `==` compares them by
address and returns `false`, just like the `AccessError` example in the previous lesson. But
when `errors.Is(a, b)` is called, the result is `true` — because `CodeError` has written the
`Is(target error) bool` method, and `errors.Is` asks `a`'s `Is` method about the target.
`a.Is(b)` confirms that `b` is also a `*CodeError` and that its `Code` field is equal; the
address never comes into play.

**EH10.** `errors.Is(a, c)`, on the other hand, returns `false`, because `c`'s `Code` field is
different — the custom `Is` method is still running, only this time it says "not equal"
according to its own logic. This shows that identity in Go is not tied to a single mechanism:
if a package-level shared variable's address is one form of identity, a type's own `Is`
method is another form of identity. `errors.Is` accepts both — it first tries `==`, then
checks whether the target or the source has an `Is` method. The sentinel error measurement in
the next section relies on the first form, the shared variable.

The choice between these two forms depends on the information the error carries. For a state
carrying no extra data at all, like `ErrNotFound`, a single shared value is enough — every
caller can look at the same address. But for an error kind carrying its own changing data,
like `CodeError` (`Code`), defining a separate shared variable ahead of time for every
possible code is not practical; the `Is` method reduces the question "is this the same type
and the same code" to a single comparison, and removes the need to produce a new sentinel for
every code.

#### Limiting Measure: Identity Is an API Promise

When a sentinel error is exported (a name starting with a capital letter, like
`ErrNotFound`), the package itself makes a promise: this value will always represent a
specific error state returned from this package with **the same pointer**. When this promise
is broken — when a change is made that produces the same state with a different `errors.New`
call — no warning appears in the source.

This promise usually breaks as a codebase grows, out of carelessness rather than bad intent.
While a package's first version returns `ErrUnauthorized` from a single place, a second
function reporting the same state gets added over time; if the person writing it writes their
own `errors.New` call instead of importing the package-level sentinel — perhaps while moving
between files, perhaps because they are unaware the sentinel exists — the two functions now
produce two separate errors carrying the same text but a different identity. The measurement
below builds this scenario exactly: `accessA` shares the sentinel, `accessB` does not.

```go
// version.go — if a sentinel error's identity changes, the caller silently falls into the wrong branch
package main

import "errors"

var ErrUnauthorized = errors.New("unauthorized access")

func accessA(rank int) error {
	if rank < 2 {
		return ErrUnauthorized
	}
	return nil
}

func accessB(rank int) error {
	if rank < 2 {
		return errors.New("unauthorized access")
	}
	return nil
}

func handle(err error) string {
	if err == ErrUnauthorized {
		return "fell into the unauthorized branch, special handling applied"
	}
	if err != nil {
		return "fell into the unknown-error branch, general handling applied"
	}
	return "no error"
}

func main() {
	observe("handle with accessA's sentinel error", "value", handle(accessA(0)))
	observe("handle with accessB's same-text error", "value", handle(accessB(0)))
	summary()
}
```

```
handle with accessA's sentinel error              value     fell into the unauthorized branch, special handling applied
handle with accessB's same-text error             value     fell into the unknown-error branch, general handling applied
binding: 0 use site, 2 value
```

**EH11.** `accessA` and `accessB` apparently report the same state to the user —
"unauthorized access" — but only `accessA` returns the real `ErrUnauthorized` sentinel. The
`handle` function has separated out a special branch with `err == ErrUnauthorized`:
`accessA`'s error falls into this branch, `accessB`'s does not and slides into the general
branch. For someone reading the source, because `accessB`'s text is exactly the same as
`accessA`'s, it is not possible to see that this distinction exists — only running it and
comparing the results shows it.

**EH12.** This shows why a sentinel error's identity is counted as an **API promise**: a
function like `accessB`, even though it thinks it reports the same state, does not keep the
promise because it does not share the sentinel. Compilation says nothing, because both return
a valid `error`; no panic or error message appears at run time either — `handle` just
silently falls into the unexpected branch. Once a package publishes a sentinel error, every
path that produces that error must return the same variable; otherwise the caller side's
identity comparison secretly starts giving the wrong result.

This also has a limit: identity is meaningful only **within the same running process**.
`ErrUnauthorized`'s address is specific to that one run of the program; writing an error to
disk and reading it back, or carrying it to another process with a network call, carries not
the `error` value but its `Error()` text — identity is lost in this crossing. This is why
sentinel error comparison is reliable only between calls within the same binary (the same
build); across a boundary (an HTTP response, a log file) the error text or a separate error
code field needs to be carried instead.

When the three findings measured in this lesson come together, the topic's measure settles,
for the first time among error values, entirely into the **value** class. Implicit
satisfaction was established at the use site, as the previous lesson also showed; but
**recognizing** an error, telling it apart from another error, rests entirely on the value's
own identity — there is neither a declaration in the source, nor a check the compiler looks
at. Identity rests either on a shared address (`ErrNotFound`) or on an `Is` method the type
writes itself; both are learned only at run time, by asking the value.

#### Summary

- A sentinel error is an `error` value defined once at the package level and exported; its
  identity stands not in its text, but in its own value.
- Every call to `errors.New` produces a new `*errors.errorString` value — even given the same
  text, two calls mean two different values, and therefore two different identities.
- On an unwrapped error, `errors.Is` and `==` give the same result; this equivalence holds
  only when the error is never wrapped.
- Text comparison (`Error() == Error()`) does not substitute for identity comparison: two
  errors with different identities can carry exactly the same text.
- A sentinel error's identity is an API promise; if every path reporting a state does not
  return the same shared variable, the caller's identity check produces the wrong result
  without any warning.
- Identity can rest not only on a shared address, but also on an `Is(target error) bool`
  method a type writes itself; `errors.Is` recognizes both, `==` only the first. Identity is
  valid only within the same running process, and is lost when crossing a boundary (writing
  to disk, carrying over a network).

#### Next Step

The errors measured in this lesson were always single-layer: a function either returned the
sentinel directly, or produced a new value in its place. But real call chains usually pass
through several layers, and each layer wants to add its own context — something like "this
error occurred while performing that operation." Once an error is wrapped this way,
`h1 == ErrNotFound` no longer works; the outer value is no longer the sentinel itself, it is
another value carrying it inside. The observation that `errors.Is` falls back to `==` on
unwrapped errors, measured in this lesson, also ends here: once a chain is added in the next
lesson, it measures why `errors.Is` now produces a different result from `==`, and how many
layers down the chain the sentinel at the bottom can still be found. The same question will
also be asked for the wrapping text itself: how is the context a layer adds preserved through
the rest of the chain?

### Error Wrapping and Unwrapping

Source: https://academia.sh/en/courses/go-type-system/error-wrapping-and-unwrapping

An error wrapped with %w stands as a chain inside the outer error, and errors.Is finds the sentinel by walking this chain layer by layer with Unwrap; when %v is used, even though the same text is produced, the chain breaks and errors.Is can no longer find it. errors.As extracts a specific target type from the chain. The limiting measure: even when the wrapping context's text is wrong, the chain's identity is not broken, and no warning is produced.

The previous lesson measured how a sentinel error's identity is preserved with `==` or with
an `Is` method — but always in single-layer calls. Real call chains usually pass through
several layers, and each layer wants to add its own context: something like "this error
occurred while performing that operation." This lesson measures how this context gets added,
and how many layers down the sentinel at the bottom of the chain can still be found.

Wrapping also offers a new observation for the topic's measure. The previous lesson measured
how an error's identity is shared; but as a call chain grows, the outer error is no longer the
sentinel **itself**, it becomes another value containing it. A comparison like
`h1 == ErrMissing` does not work in such a chain — neither does the outer value carry the same
address as the sentinel, nor does its text stay the same. This lesson shows how identity can
still be queried once it is embedded in a chain.

#### The Bottom of the Chain: Identity Carried With `%w`

When `fmt.Errorf` is called with the `%w` formatting verb, it produces a new error, and this
new error holds the given error **inside** itself. The three-layer chain below builds this in
two forms: the `Sound` version uses `%w` at every layer, the `Broken` version uses `%v` at the
middle layer — both produce the same text.

Three layers were chosen deliberately here: in a real codebase, a storage layer, a service
layer calling it, and a handler layer calling that service build a similar chain. Each layer
adds a sentence from its own point of view (not "record not found" but something like "user
could not be authenticated: record not found"), but the question of what the bottommost cause
is does not change. The measurement tests whether the answer to this question is **preserved**
throughout the chain.

```go
// binding.go — is the error binding established at the use site, or inside the value
package main

import "fmt"

var useSite, value int

func observe(name, location, evidence string) {
	switch location {
	case "use":
		useSite++
	default:
		value++
	}
	fmt.Printf("%-50s %-9s %s\n", name, location, evidence)
}

func summary() {
	fmt.Printf("binding: %d use site, %d value\n", useSite, value)
}
```

```go
// chain.go — three-layer wrapping: the sentinel error is found even at the bottom of the chain, %v cuts the chain
package main

import (
	"errors"
	"fmt"
)

var ErrMissing = errors.New("no record")

func layer1() error {
	return ErrMissing
}

func layer2Sound() error {
	if err := layer1(); err != nil {
		return fmt.Errorf("layer2 failed: %w", err)
	}
	return nil
}

func layer3Sound() error {
	if err := layer2Sound(); err != nil {
		return fmt.Errorf("layer3 failed: %w", err)
	}
	return nil
}

func layer2Broken() error {
	if err := layer1(); err != nil {
		return fmt.Errorf("layer2 failed: %v", err)
	}
	return nil
}

func layer3Broken() error {
	if err := layer2Broken(); err != nil {
		return fmt.Errorf("layer3 failed: %w", err)
	}
	return nil
}

func main() {
	sound := layer3Sound()
	broken := layer3Broken()

	observe("sound chain's outer text", "value", sound.Error())
	observe("broken chain's outer text", "value", broken.Error())
	observe("is the sentinel found in the sound chain", "value", fmt.Sprint(errors.Is(sound, ErrMissing)))
	observe("is the sentinel found in the broken chain", "value", fmt.Sprint(errors.Is(broken, ErrMissing)))
	summary()
}
```

```
sound chain's outer text                           value     layer3 failed: layer2 failed: no record
broken chain's outer text                          value     layer3 failed: layer2 failed: no record
is the sentinel found in the sound chain           value     true
is the sentinel found in the broken chain          value     false
binding: 0 use site, 4 value
```

**EH13.** The first two lines are one of the course's clearest demonstrations: the two
chains' **text is exactly the same**, because both `%w` and `%v` place the same
`Error() string` output inside the new text `fmt.Errorf` produces. Someone looking at the text
cannot tell the two errors apart. But the third and fourth lines give an entirely different
result: `errors.Is(sound, ErrMissing)` is `true`, `errors.Is(broken, ErrMissing)` is `false`.
The text stays the same while the query result changes — because the question `errors.Is`
asks looks not at the text, but at the chain.

**EH14.** The difference is in a single behavior between `%w` and `%v`: `%w` gives the new
error `fmt.Errorf` produces an `Unwrap() error` method, and this method returns the given
error; `%v` only embeds the text, it adds no method. The error `layer2Broken` produces has no
`Unwrap` — the chain cuts off right there, without reaching `ErrMissing`. `layer3Broken` using
its own `%w` does not compensate for this break, because the place it broke is already one
layer below.

This shows that error wrapping is a **one-directional** chain: a layer above cannot fix a
layer below it breaking the chain. `layer3Broken` wraps the error `layer2Broken` produces with
`%w`, and correctly builds its own Unwrap method — but the value it wraps **itself** already
carries no link to `ErrMissing` at the bottom. The chain's soundness depends on its weakest
layer; a single use of `%v` makes every layer above it behaving correctly meaningless.

#### Walking Inside the Chain: `errors.Unwrap`

What `errors.Is` does is actually built on a single primitive: `errors.Unwrap`. This function
takes an error and returns one layer below it if that error has written an
`Unwrap() error` method; it returns `nil` if it has not. `errors.Is` calls this in a loop
until it finds the target or reaches `nil`. The measurement below builds this loop by hand.

This shows that `errors.Is` is not a "magic" behavior, it is built on an ordinary loop and an
ordinary method call. The chain can be built because `%w` gives an error the `Unwrap` method;
without this method, `errors.Unwrap` would return `nil` on the first call, and the bottom of
the chain could never be reached. The error `layer2Broken` produces in the previous section is
exactly in this situation: because it was produced with `%v`, it has no `Unwrap` method;
`errors.Unwrap` returns `nil` when called on it.

```go
// stepbystep.go — manually walking, step by step with Unwrap, the same chain errors.Is walks
package main

import (
	"errors"
	"fmt"
)

var ErrMissing = errors.New("no record")

func main() {
	wrapped := fmt.Errorf("outer layer: %w", fmt.Errorf("middle layer: %w", ErrMissing))

	step := 0
	current := wrapped
	for current != nil {
		observe(fmt.Sprintf("chain step %d", step), "value", current.Error())
		current = errors.Unwrap(current)
		step++
	}
	observe("does errors.Is find the same chain in one call", "value", fmt.Sprint(errors.Is(wrapped, ErrMissing)))
	summary()
}
```

```
chain step 0                                       value     outer layer: middle layer: no record
chain step 1                                       value     middle layer: no record
chain step 2                                       value     no record
does errors.Is find the same chain in one call     value     true
binding: 0 use site, 4 value
```

**EH15.** The loop stops at three steps: `current` carries, in turn, the outer error, the
middle error, and `ErrMissing` at the very bottom; on the fourth call,
`errors.Unwrap(ErrMissing)` returns `nil` because `ErrMissing` was produced with `errors.New`
and has no `Unwrap` method — the loop stops there. Each step's text is the previous one's
**final piece**; this shows that the chain really stands nested, carried not as a single long
string but as separate, linked error values. `errors.Is(wrapped, ErrMissing)` does these three
steps in a single call and returns `true` — the same mechanism as the `sound` chain in the
previous section.

This step-by-step walk also shows `errors.Is`'s cost: the longer the chain, the more times
`errors.Unwrap` gets called. In practice, this cost stays at a negligible level in a call
chain a few layers deep — but the chain not entering an infinite loop also depends on
`Unwrap` genuinely returning a **different** value at every step. An error wrapping itself
(`Unwrap` returning the same value) is not measured here, but it explains why the chain was
designed as a structure that always **advances**, never turning back.

#### Extracting a Type From the Chain: `errors.As`

`errors.Is` asks whether a target **value** is in the chain; `errors.As`, on the other hand,
asks whether there is an error of a specific **type** in the chain, and if there is, writes it
into a variable. This shows that the chain preserves not only identity, but also the **data**
it carries.

This distinction also overlaps with the previous lesson's two forms of identity. For a
sentinel carrying no data, like `ErrMissing`, the question is always "is this the value" —
`errors.Is` is enough. But for an error kind with its own field, like `FieldError`, the real
question is usually "is this an error of this kind, and what is its field" — `errors.As`
answers this question. This is also why `errors.As`'s second argument takes a pointer to a
pointer: it needs to be able to write the value it finds directly into the caller's variable.

```go
// target.go — extracting a target type from the chain; the wrong context does not break the chain's identity
package main

import (
	"errors"
	"fmt"
)

var ErrMissing = errors.New("no record")

type FieldError struct{ Field string }

func (a *FieldError) Error() string { return "invalid field: " + a.Field }

func validate() error {
	return fmt.Errorf("record could not be validated: %w", &FieldError{Field: "name"})
}

func layer1() error {
	return ErrMissing
}

func layerWrongContext() error {
	if err := layer1(); err != nil {
		return fmt.Errorf("unexpected network error: %w", err)
	}
	return nil
}

func main() {
	var target *FieldError
	found := errors.As(validate(), &target)
	observe("extracting the FieldError type from the chain", "value", fmt.Sprint(found, " ", target.Field))

	wrong := layerWrongContext()
	observe("text of the error wrapped with the wrong context", "value", wrong.Error())
	observe("is identity still found despite the wrong context", "value", fmt.Sprint(errors.Is(wrong, ErrMissing)))
	summary()
}
```

```
extracting the FieldError type from the chain      value     true name
text of the error wrapped with the wrong context   value     unexpected network error: no record
is identity still found despite the wrong context  value     true
binding: 0 use site, 3 value
```

**EH16.** `validate` wraps a `*FieldError` at the top of the chain.
`errors.As(validate(), &target)` walks the chain with `errors.Unwrap`, looks for an error of
type `*FieldError`, finds it, and **assigns** it to `target` — afterward, `target.Field` can
be read directly. This is something `errors.Is` cannot give: `errors.Is` only asks "does this
value exist in the chain"; `errors.As` says "does a value of this type exist, and if so, give
it to me." Both walk the same `Unwrap` chain, asking a different question.

The `validate` function's return type is also worth noting here: its signature says only
`error`, not `*FieldError` — doing the reverse of the trap measured in the Error Interface
lesson. The concrete type does not appear in the signature, it is embedded only inside the
chain; if the caller wants to know it, it has to query the chain with `errors.As`. The
signature itself makes no promise at all about which types stand inside the chain.

**EH17.** The second measurement builds this lesson's limiting observation.
`layerWrongContext` writes "unexpected network error" into the text even though the real
cause is `ErrMissing` — the context is wrong, even misleading. But
`errors.Is(wrong, ErrMissing)` still returns `true`: the chain is still sound, because `%w`
was used and the `Unwrap` method still reaches `ErrMissing`. The chain preserves **identity**,
but never checks the **correctness** of the wrapping text — writing a wrong context produces
no warning at either compile time or run time, because `fmt.Errorf`'s first argument is an
ordinary string, and the language has no check that looks at it.

The practical consequence of this observation is clear: a check written with `errors.Is` and
`errors.As` keeps working correctly without looking at what the wrapping text says — this is
an advantage, because the text can be changed freely for human reading; identity checking is
not affected by this. But the same property also carries a risk: when a developer adds a
wrong or misleading context to the chain, the only way to notice it is to **read** the text;
neither `errors.Is` nor `errors.As` gives a warning, because both look only at identity and
type.

These three measurements connect to the topic's common measure in the same way: a sentinel
error's identity (previous lesson), the text an error wrapping carries, and extracting a type
from a wrapped chain — all three are carried **inside the value**, appearing with no
declaration in the source. This is the error-chain counterpart of the common measurement
where eight of the sixteen bindings are carried in the value.

The picture these three lessons (The Error Interface, Sentinel Errors, Error Wrapping and
Unwrapping) build together is this: `error` is implicitly satisfied as an interface, and the
satisfying type is never mentioned in the source (use site); an error's identity rests on an
address or on an `Is` method (value); and this identity, even when carried wrapped between
layers, is not lost, thanks to the `Unwrap` chain (value again). Someone reading the source
cannot look at either a declaration or a signature to see which sentinel stands at the bottom
of a three-layer chain — it is enough only to actually walk the chain (or to have `errors.Is`
do that walking).

#### Summary

- `%w` adds an `Unwrap() error` method to the error `fmt.Errorf` produces; `%v` does
  not — even if the text stays the same, the chain breaks at the layer where `%v` is used.
- `errors.Is` calls `errors.Unwrap` in a loop until it finds the target or reaches `nil`; if
  the chain has three layers, it is walked in three steps.
- `errors.As` looks for an error of a specific type in the chain and, when it finds one,
  writes it into a variable; it carries data `errors.Is` cannot give (such as a field like
  `Field`) this way.
- The chain's identity (`errors.Is`, `errors.As`) is preserved, but the wrapping text's
  correctness is never checked — writing a wrong context produces no warning.
- A sentinel error's identity, the text an error wrapping carries, and extracting a type from
  the chain are all three carried inside the value; no declaration in the source shows this
  binding.
- `errors.Unwrap` not entering an infinite loop depends on every layer carrying a different
  value beneath it; the chain always advances, it never turns back on itself.

#### Next Step

Every layer measured in this lesson **returned** an `error` by wrapping its own context — the
return type always stayed the same, the signature never changed. No matter how long the chain
got, every function's signature still had a single contract: the last value is an `error`.
But in Go, there is also a way for a function to fail that never appears in the signature at
all: panic. When a function calls `panic`, no trace remains in the return type — there is no
difference in signature between `func F() int` and `func F() int`, one of them always returns
an integer while the other can crash at run time. The next lesson measures this exit: if a
function panics instead of returning an error, where does this choice appear in the
signature, and how can the caller know it in advance?

### Panic and Recovery

Source: https://academia.sh/en/courses/go-type-system/panic-and-recover

A function can panic instead of returning an error, and this choice leaves no trace in the signature: of two functions, one returning (int, error) and the other returning only int, one always produces a value while the other can crash at run time. recover only works in the deferred function that calls it directly; if a recovered panic is not converted into an error, the information is lost entirely. No stack trace is printed; only panic=true/false and the recovered value's type are measured.

The previous three lessons measured that a function always reports its failure with the same
contract: the last return value is an `error`. No matter how long the chain grew, no matter how
much context each layer added, the signature always stayed the same. This lesson steps outside
that contract: in Go, there is also a way for a function to fail that never appears in the
signature at all: panic. This lesson's question is the topic's question in its sharpest form: if
panic's binding is never established at all, how can the caller know this in advance?

Panic is a mechanism the language's own run time also uses: reaching outside a slice's bounds,
reading from an uninitialized pointer before writing to a map, dividing by zero — all of these
are panics produced by the run time itself. This lesson measures only the `panic` calls a
programmer writes directly, but the observation is the same for both: neither appears in the
signature.

#### The Channel Visible in the Signature, the Exit That Is Not

Writing the same task in two forms is possible: a `Store` type's get function could either
return `(int, error)` and carry the not-found state in the second value, or return only `int`
and panic when the item is not found. The measurement below builds both forms in a three-layer
call chain — `inner`, `middle` calling it, `outer` calling that. Three layers were chosen
deliberately: the same depth as the chain in the Error Wrapping and Unwrapping lesson, but this
time the layers add no context at all, they only forward the call — what is measured is not how
context is carried, but how visible the failure channel itself stays across the layers.

```go
// binding.go — is panic's binding established in a declaration, inside the value, or nowhere at all
package main

import "fmt"

var declaration, value, none int

func observe(name, location, evidence string) {
	switch location {
	case "declaration":
		declaration++
	case "value":
		value++
	default:
		none++
	}
	fmt.Printf("%-54s %-12s %s\n", name, location, evidence)
}

func summary() {
	fmt.Printf("binding: %d declaration, %d value, %d nowhere\n", declaration, value, none)
}
```

```go
// forms.go — the same task in two forms: error-returning and panicking, a three-layer chain
package main

import "fmt"

type Store struct{ items map[string]int }

func (s Store) innerError(name string) (int, error) {
	v, ok := s.items[name]
	if !ok {
		return 0, fmt.Errorf("not found: %s", name)
	}
	return v, nil
}
func (s Store) middleError(name string) (int, error) { return s.innerError(name) }
func (s Store) outerError(name string) (int, error)  { return s.middleError(name) }

func (s Store) innerPanic(name string) int {
	v, ok := s.items[name]
	if !ok {
		panic(fmt.Sprintf("not found: %s", name))
	}
	return v
}
func (s Store) middlePanic(name string) int { return s.innerPanic(name) }
func (s Store) outerPanic(name string) int  { return s.middlePanic(name) }

func panics(f func()) (p bool) {
	defer func() {
		if recover() != nil {
			p = true
		}
	}()
	f()
	return false
}

func main() {
	store := Store{items: map[string]int{"a": 1}}

	result, err := store.outerError("missing")
	observe("the error-returning signature's visible channel", "declaration", "(int, error) -- the second value carries the error")
	observe("the error-returning call passing through three layers", "value", fmt.Sprint(result, " ", err))

	panicked := panics(func() { store.outerPanic("missing") })
	observe("the panicking signature's visible channel", "none", "int -- there is no second channel")
	observe("the panicking call passing through three layers", "none", fmt.Sprint("panic=", panicked))

	summary()
}
```

```
the error-returning signature's visible channel        declaration  (int, error) -- the second value carries the error
the error-returning call passing through three layers  value        0 not found: missing
the panicking signature's visible channel              none         int -- there is no second channel
the panicking call passing through three layers        none         panic=true
binding: 1 declaration, 1 value, 2 nowhere
```

**EH18.** `innerError`'s signature is `(int, error)` — the failure channel stands directly in
the **declaration**; `middle` and `outer` carry this second value unchanged. `innerPanic`'s
signature, on the other hand, is only `int`; there is no second slot for failure. The `panics`
tool (like `compiles`, borrowed from the Functions lesson, from K01's toolkit) here observes not
merely whether the call passed, but whether it panicked, and it never prints a stack trace. The
call passing through the third layer really has panicked; but **seeing** this required writing a
wrapper like `panics` — an ordinary call (`store.outerPanic("missing")`) crashes without giving
any sign.

**EH19.** These two lines build the lesson's central observation: in the error-returning form,
the failure channel occupies **a place in the signature** (declaration); in the panicking form,
it occupies **no place at all**. This is a fourth case that falls outside the topic's
declaration / use site / value triad — the binding is neither written in the source, nor
something the compiler checks at an assignment point, nor a pair the value carries. Someone
calling `innerPanic` cannot learn from the signature that this function might fail; the only way
to learn this is either documentation (a text outside the source) or reading the function's
body.

This asymmetry has a cost, but the cost runs in both directions. In the error-returning form,
every layer (`inner`, `middle`, `outer`) has to carry the second value by hand — writing
`return ..., err` three times across three lines, or at least noticing the second value three
times. In the panicking form, the layers write nothing at all; an ordinary function call is
enough, because the panic propagates on its own. The cost of visibility is a repeated piece of
writing at every layer; the cost of invisibility is that the caller is left with no warning at
all.

#### Recovery Only Works in the Deferred Call

`recover` is the only way to stop a panic and return to normal execution — but it only works in
a single position: **directly inside** a function deferred with `defer`. When the `recover` call
is moved into a helper function, `recover` no longer catches anything, even though that helper
is called by the deferred function.

This restriction is not arbitrary: `recover` itself asks the run time which frame of the call
stack it is being called from, and only returns something if that frame is **the frame the
panicking function directly deferred**. This explains why burying `recover` inside an ordinary
helper function to make it reusable — a "write once, use everywhere" shortcut — mostly does not
work.

```go
// recover.go — recover only works inside the defer that calls it directly
package main

import "fmt"

func panics(f func()) (p bool) {
	defer func() {
		if recover() != nil {
			p = true
		}
	}()
	f()
	return false
}

func directRecover() (recovered bool) {
	defer func() {
		if recover() != nil {
			recovered = true
		}
	}()
	panic("test")
}

func indirectRecoverHelper() interface{} {
	return recover()
}

func indirectRecover() (recovered bool) {
	defer func() {
		r := indirectRecoverHelper()
		if r != nil {
			recovered = true
		}
	}()
	panic("test")
}

func main() {
	a := directRecover()
	observe("recover called directly inside defer", "value", fmt.Sprint(a))

	escaped := panics(func() { indirectRecover() })
	observe("recover called through a helper, did it escape outward", "value", fmt.Sprint(escaped))
	summary()
}
```

```
recover called directly inside defer                   value        true
recover called through a helper, did it escape outward value        true
binding: 0 declaration, 2 value, 0 nowhere
```

**EH20.** `directRecover` calls `recover()` directly in the body of
`defer func() { ... }()` and catches the panic — `recovered` comes back `true`.
`indirectRecover`, on the other hand, looks as though it is trying to do the same thing: it has
written a `defer`, that `defer` calls a function, and that function calls `recover()`. But
because there is a call layer in between, `recover` sees that the function calling it is **not**
deferred and returns `nil` — the panic is not caught, `indirectRecover` escapes outward on its
own. Seeing this escape required calling it wrapped in a `panics` wrapper; the second line shows
that this outer wrapper catches the panic.

**EH21.** This shows that `recover` is one of the most narrowly defined places in the language:
it only looks at the question "is the function currently running the function the panicking call
directly deferred." Call depth does not matter — `directRecover` itself could also have been
called inside `panics`; the result would not change; what matters is that the function
**calling** `recover` is itself deferred with `defer`. This rule explains why attempts to make
`recover` reusable by moving it into a helper function fail.

#### Limiting Measure: Recovery Does Not Destroy the Panic, It Carries It

When a panic is recovered, the information it carries (the value given to panic) is not
lost — `recover()` returns it. But **using** this information is the caller's responsibility.
The two functions below catch the same panic; one converts it into an `error`, the other
discards it silently.

These two functions also show the bridge between panic and error: once a panic is recovered, it
is entirely possible to put the result back into Go's ordinary error contract — the
`(result, error)` form. `runRecovering` shows a pattern commonly used to hide a subsystem that
panics behind a library's outer surface and present its caller with an ordinary `error`.

```go
// recovery.go — recovery does not destroy the panic, it carries it; if not converted to an error, the information is lost
package main

import (
	"fmt"
	"strings"
)

func vowelCount(word string) int {
	if word == "" {
		panic("empty word")
	}
	count := 0
	for _, r := range word {
		if strings.ContainsRune("aeiouAEIOU", r) {
			count++
		}
	}
	return count
}

func runRecovering(f func() int) (result int, err error) {
	defer func() {
		if r := recover(); r != nil {
			err = fmt.Errorf("recovered panic (%T): %v", r, r)
		}
	}()
	return f(), nil
}

func runSilently(f func() int) (result int) {
	defer func() {
		recover()
	}()
	return f()
}

func main() {
	result1, err1 := runRecovering(func() int { return vowelCount("") })
	observe("the empty word's recovered panic converted to an error", "value", fmt.Sprint(result1, " ", err1))

	emptyResult := runSilently(func() int { return vowelCount("") })
	realZeroResult := runSilently(func() int { return vowelCount("krt") })
	observe("the empty word's recovered result", "none", fmt.Sprint(emptyResult))
	observe("the vowel-less real word's result", "none", fmt.Sprint(realZeroResult))

	summary()
}
```

```
the empty word's recovered panic converted to an error value        0 recovered panic (string): empty word
the empty word's recovered result                      none         0
the vowel-less real word's result                      none         0
binding: 0 declaration, 1 value, 2 nowhere
```

**EH22.** `runRecovering` converts the value it recovers, together with its type via `%T`, into
an `error` — the value `vowelCount("")` gives when it panics is a string (`string`), and this
appears in the returned error. Recovery does not **destroy** the panic; it carries the
information it holds into another form, into a channel (`error`) the signature already has.
This line proves that even once a panic is recovered, `%T` can still show which value was
panicked with.

**EH23.** `runSilently`, on the other hand, catches the same panic but writes the value
`recover()` returns nowhere. The result: the `0` returned when `vowelCount("")` panics and is
recovered, and the `0` returned by `vowelCount("krt")` (a genuinely computed string containing no
vowels), are **indistinguishable** from each other. This is the lesson's limiting measure:
recovery carries the information, but if the recovered value is not written anywhere, that
carried information is lost entirely, somewhere the caller cannot reach — what remains looks
like a zero that "seems successful." In this example, even the zero itself is a trap:
`vowelCount("krt")` is a genuinely computed, legitimate zero; there is no signal difference
between it and a panic-derived zero. In general, a return value left silently after recovery can
**always** be confused with its type's zero value — a risk that holds no matter which type is
chosen.

These three measurements together give a definite answer to the topic's question: panic's
binding is not in the declaration (there is no trace in the signature), not in the use site
(there is no assignment point the compiler looks at), and not in the value either (the caller
cannot read the possibility of panicking from any type) — panic is established in **none** of
the topic's three places. Its only establishment point is the run time, which never appears at
all until the recovery code runs; and even recovery compensates for nothing unless the
information is carried by hand into another channel.

#### Summary

- An error-returning function's signature sets aside a channel for failure (`declaration`); a
  panicking function's signature has no such channel at all — panic is established in none of
  the topic's three places.
- `recover` only works inside the deferred (`defer`) function that calls it directly; once moved
  into a helper function, it returns `nil` and the panic keeps propagating uncaught.
- When a panic is recovered, the value it carries is not lost; it can be read together with its
  type via `%T` and converted into an `error`.
- If the recovered value is not written anywhere, the information is lost entirely: a recovered
  panic's zero result becomes indistinguishable from a genuine zero result.
- No measurement prints a stack trace, a memory address or a goroutine number; only
  `panic=true`/`panic=false` and the recovered value's type read with `%T` are recorded.

#### Next Step

This topic asked a single question four times: how does a type satisfy the `error` interface,
where does an error's identity stand, how is a chain's context preserved, and finally, what
happens when an exit's binding is never established at all. The first three lessons all found
the same answer: the binding is established **at the use site** (an assignment to a variable, a
method-set check) or carried **inside the value** (the dynamic type, identity, the wrapped
chain). This lesson measured a fourth case that falls outside both of these: panic's binding is
established nowhere at all and never appears in the signature — the caller cannot learn from a
function's signature that it might panic.

The next topic, Generics, does exactly the opposite of this. A generic function's type parameter
is also never written in the source — the caller can call a function without specifying any type
at all. But unlike panic, this binding is established by the **compiler**, and exactly at the
call site: every call determines, at compile time, which type it is instantiated with. The same
"not written in the source" observation reaches two exactly opposite conclusions in the two
topics — panic escapes every check, generics carries every check to the use site, and the
binding never enters a value.

## Generics

The type parameter being inferred at the call site, type constraints being declared as interfaces with type sets and unions, the instantiation of type-safe containers sitting in the caller's declaration, and the measurable difference between doing the same job with an interface or with generics.

### Generic Functions

Source: https://academia.sh/en/courses/go-type-system/generic-functions

A generic function's type parameter is never written in the source at all, but the compiler builds it at the call site by inference: the same firstElement source, called three times with int, string, and float64 slices, gives %T three different answers. The limiting measurement shows that an unconstrained type parameter allows almost no operation in the body at all: == and + do not compile, only type-independent operations such as len pass.

The Panic and Recover lesson measured a binding built nowhere at all: the fact that a panic
exists never appears in the caller's signature at all; the caller only learns of it while the
program is running. This topic does the reverse. A generic function's type parameter is also
never written in the source at all — when `firstElement[T any](s []T)` is called, what `T` will
be never appears on any call line — but the binding does not stay unresolved: the compiler
builds it **at the call site**, by looking at the type of the arguments, and the binding it
builds never enters **inside the value** at all. This asks the course's question once again:
where is this binding built?

In the common measurement, four of sixteen bindings sit in the declaration, four at the use
site, eight inside the value; the type parameter inference that is this lesson's subject is one
of the four bindings built at the use site. Generics and interfaces answer the same question in
two separate places — in an interface the binding enters the value, in a generic it never does —
and this difference is the subject of the course's third claim; this lesson builds the first
half of that difference, the use site.

Before generics, the same job had to be written one of two ways: either a separate function for
every type (`firstElementInt`, `firstElementString`, `firstElementFloat`), or a single function
taking the parameter as the empty interface (`any`) and converting back to the concrete type in
the body with a type assertion. The first duplicates the source — four copies of the same logic,
four separate places each requiring maintenance. The second keeps the source single but pushes
type information from compile time to run time: the function accepts whatever type the caller
passes; a wrong type surfaces only at run time, when a type assertion fails. A generic function
opens a third path: the source stays single, but type checking stays at compile time —
`firstElement` called with a `[]bool` is rejected before that line ever compiles; `T` is written
once in the body and is checked separately for every call. Where these three writings bind the
same job — by duplicating it in the declaration, by delaying it into the value, by compiling it
at the use site — is the subject of the course's closing lesson; this lesson builds only the
third.

#### The Type Parameter Is Built at the Call Site

A generic function adds a **type parameter** list in square brackets right after its name:
`func firstElement[T any](s []T) (T, bool)`. `T` here is not a concrete type, it is a
placeholder to be filled in when the function is called; `any` states the boundary of types this
placeholder accepts — for now there is no boundary, every type is valid. The function itself is
written in a single source, and this source is independent of which concrete type it will be
**instantiated** with.

**GE1.** The `var zero T` line in the function body is a zero-value production written with no
knowledge at all of what `T` will be; this line sits once in the source and stays exactly the
same on every call — it is a **declaration**. `T` itself changes on every call, and nowhere at
all in the source is it written where this change happens; the compiler builds it only by
looking at the type of the argument on the call line — it is a **use-site** binding. The same
function's two parts fall into two separate classes of the course.

```go
// inference.go — the type parameter is inferred at the call site, the zero-value logic sits in the declaration
package main

import "fmt"

func observe(name, location, evidence string) {
	fmt.Printf("%-42s %-13s %s\n", name, location, evidence)
}

// firstElement runs from the same source for every type; the "var zero T" line sits
// in the function's own declaration and never knows which concrete type will come.
func firstElement[T any](s []T) (T, bool) {
	var zero T
	if len(s) == 0 {
		return zero, false
	}
	return s[0], true
}

func zeroValue[T any]() T {
	var z T
	return z
}

func main() {
	fmt.Println("-- where the binding is made: a generic function --")

	i, _ := firstElement([]int{7, 2, 9})
	observe("type parameter (int slice)", "use site", fmt.Sprintf("inferred, T=%T", i))

	s, _ := firstElement([]string{"apple", "pear"})
	observe("type parameter (string slice)", "use site", fmt.Sprintf("inferred, T=%T", s))

	f, _ := firstElement([]float64{1.5, 2.5})
	observe("type parameter (float slice)", "use site", fmt.Sprintf("inferred, T=%T", f))

	observe("zero-value logic (var zero T)", "declaration", "in firstElement's own source, always the same line")

	empty := zeroValue[string]()
	observe("call inference cannot handle", "use site", fmt.Sprintf("zeroValue[string]() written explicitly, %T", empty))
}
```

```
-- where the binding is made: a generic function --
type parameter (int slice)                 use site      inferred, T=int
type parameter (string slice)              use site      inferred, T=string
type parameter (float slice)               use site      inferred, T=float64
zero-value logic (var zero T)              declaration   in firstElement's own source, always the same line
call inference cannot handle               use site      zeroValue[string]() written explicitly, string
```

Every call is an **instantiation** of the generic function: `firstElement`, instantiated with
`T=int`, corresponds to a function operating on an `int` slice; instantiated with `T=string`, to
another function operating on a string slice — both from the same source, but with a different
type bound. The word instantiation will come up twice more after this lesson: in the Type
Constraints lesson, which instantiations a constraint rejects will be measured; in the Generic
Data Structures lesson, whose declaration an instantiation sits in will be measured. In this
lesson, instantiation always happens on the **caller's** side, by looking at the argument's
static type — the function itself does not know which instantiations will be requested.

The `var zero T` line giving three different results across three separate instantiations comes
from the same mechanism. The Zero Values lesson (M09/K01) showed that every type finds its own
zero value defined — `0` for `int`, an empty string for `string`, `0.0` for `float64`. `var zero
T` carries this definedness into the generic function: the source never says which zero value
will be produced at all, it only says "let `T`'s zero value sit here," and the compiler fills
this promise once instantiation is built. While this line itself stays fixed in the declaration,
the value it fills comes from the use site at every instantiation — a small repetition of the
same distinction.

**GE2.** The `firstElement` source stays unchanged, not a single letter different, across three
calls; the only thing that changes is the element type of the slice the argument carries. `%T`
gives three different answers — `int`, `string`, `float64` — but none of these three answers can
be found by searching the source, because the source never writes a name in `T`'s place at all.
The compiler builds this binding, just like the Interface Concept lesson's implicit
satisfaction, **outside the source** — but unlike an interface, it does not leave this decision
to run time, it gives it at compile time, by looking at the argument's static type. This is what
the two bindings have in common: both are built at the use site. Their difference starts right
here too: interface conformance can be verified by asking the value (with `%T`), while a generic
function's type parameter disappears once compilation finishes and is stored nowhere at all in
the running program — this distinction is measured again in the When Generics lesson.

#### Where Inference Falls Short

The compiler can read the type parameter only from the **arguments**; if there is no argument,
there is nothing to read either. `zeroValue[T any]() T` is the plainest example of this: the
function has no parameter at all, only a return type, and inference cannot be worked backward
from a return type. In the run above, the call `zeroValue[string]()` therefore carries `T`
**explicitly** in square brackets for exactly this reason; deleting `[string]` from this line
turns "which type's zero value" into a question the compiler cannot answer, and the source does
not compile.

A second case where inference falls short can arise even when an argument **does exist**: if the
same type parameter appears in more than one parameter, the compiler expects all of them to
resolve to the **same** concrete type. The measurement below tests this by asking the compiler
directly from within the program.

```go
// limits.go — the call inference cannot handle and the limits of an unconstrained body, measured by asking the compiler
package main

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

func compiles(source string) bool {
	dir, err := os.MkdirTemp("", "trial")
	if err != nil {
		return false
	}
	defer os.RemoveAll(dir)
	os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module trial\n\ngo 1.24\n"), 0o644)
	os.WriteFile(filepath.Join(dir, "code.go"), []byte(source), 0o644)
	cmd := exec.Command("go", "build", "./...")
	cmd.Dir = dir
	cmd.Env = append(os.Environ(), "GOTOOLCHAIN=local")
	return cmd.Run() == nil
}

func classifySource(source string) string {
	if compiles(source) {
		return "compiled"
	}
	return "did not compile"
}

func main() {
	mismatchedCall := `package trial
func Both[T any](a, b T) T { return a }
func F() { Both(3, "x") }`

	explicitCall := `package trial
func Both[T any](a, b T) T { return a }
func F() { Both[any](3, "x") }`

	comparison := `package trial
func Equal[T any](a, b T) bool { return a == b }`

	addition := `package trial
func Add[T any](a, b T) T { return a + b }`

	length := `package trial
func Length[T any](s []T) int { return len(s) }`

	fmt.Printf("%-42s %s\n", "operation", "result")
	fmt.Printf("%-42s %s\n", "inference: int and string to the same T", classifySource(mismatchedCall))
	fmt.Printf("%-42s %s\n", "when T=any is written explicitly", classifySource(explicitCall))
	fmt.Printf("%-42s %s\n", "unconstrained T in the body with == ", classifySource(comparison))
	fmt.Printf("%-42s %s\n", "unconstrained T in the body with +", classifySource(addition))
	fmt.Printf("%-42s %s\n", "len on an unconstrained T's slice", classifySource(length))
}
```

```
operation                                  result
inference: int and string to the same T    did not compile
when T=any is written explicitly           compiled
unconstrained T in the body with ==        did not compile
unconstrained T in the body with +         did not compile
len on an unconstrained T's slice          compiled
```

**GE3.** The first two lines are two spellings of the same call: `Both(3, "x")` does not compile
because the compiler tries to read `T` as `int` from the first argument and `string` from the
second, and cannot settle on a single type; `Both[any](3, "x")` compiles because the type
parameter is no longer left to inference at all, the caller writes `any` **itself**. Inference is
a convenience, not a requirement — the compiler always allows falling back to explicit writing;
only where inference **cannot be done** does explicit writing become mandatory.

This rejection comes from two parameters sharing the **same** type parameter — the signature
`func Both[T any](a, b T) T` imposes, as a declaration, that `a` and `b` resolve to the same
concrete type. If the signature had been written `func Both2[T1, T2 any](a T1, b T2)`, `a` would
bind to `int` and `b` to `string` separately, and the call would compile; whether two variables
are allowed to be different types depends on **how many separate names** the type parameter list
carries. This confirms the binding's position once again: how many type parameters will be
shared is written in the source (declaration); which concrete types those parameters correspond
to is built anew on every call (use site).

This distinction turns into a design decision in practice: if two parameters **have to** be the
same type — for example in a comparison or a swap function — sharing a single type parameter
hands this requirement over to the compiler; if the caller mistakenly passes two different
types, the source does not compile at all. If two parameters **have to** be independent, opening
separate type parameters prevents the compiler from imposing an unnecessary equality. The type
parameter list in the source is therefore not merely a syntax detail, it is a small contract
stating which arguments are tied to each other.

#### The Limiting Measurement: What Can Be Written in an Unconstrained Body

**GE4.** The last three lines measure a different question: once the type parameter is built at
the call site, what can be done with that parameter **in the function's body**? `any` only means
"every type is accepted," not "every operation can be written." `a == b` does not compile,
because `T`'s constraint is `any` and `any` guarantees no comparability — a slice or a function
also satisfies `any`, but neither can be compared with `==`. `a + b` does not compile for the
same reason: `+` is defined only for numeric types and for strings; `any` guarantees none of
that. `len(s)` does compile, because this operation looks not at `T` itself but at the slice's
type — the length of `s` is independent of the element type.

These three lines show that an unconstrained type parameter is nearly useless in the body: `any`
provides the widest acceptance but leaves the narrowest set of operations. This restriction is
not a shortcoming, it is the flip side of the course's second claim: if a type implicitly
satisfying an interface is never written anywhere in the source, then the fact that the compiler
knows **nothing at all** about `T` is also never written anywhere in the source; `any` carries
this ignorance to its most extreme point.

The reason the `len(s)` line behaves differently from the other two is that this operation looks
not at `T` but at the slice's **own structure**: a slice, whatever its element type, carries the
same three-field header, and the length is read from that header. Other built-in operations such
as `index`, `range`, `cap` belong to the same class — they never ask what type `T` is at all,
they only look at the structure the language itself guarantees. `==` and `+` are the exact
opposite: both call a behavior specific to `T`'s concrete type, and an unconstrained `T` does not
even guarantee that this behavior **exists**. The next lesson builds the mechanism that fills this
gap — the type constraint, which states in the **declaration** which operations a type
parameter allows; the built-in constraint named `comparable` makes the `Equal` example above
compile with a single word.

The entire five-line measurement was produced with a single tool named `compiles`: the same
method — writing the source to a temporary package and calling the compiler as a subprocess,
reading only the exit status — was also used in the Go's Design Goals lesson. The method carries
the same two properties here as well — deterministic (the same source gives the same answer on
every run, because the compiler itself is being called) and portable (it depends only on the
standard library's process-launching capability). Never reading the error text is a deliberate
boundary: this lesson measures not "why was it rejected" but "was it rejected," because the form
of the error text the compiler produces is a version-dependent detail, and the course's
criterion does not rest on that detail.

#### Summary

- A generic function's type parameter is never written anywhere in the source at all; the
  compiler builds it at the call site, by looking at the static type of the arguments.
- Type-independent code, such as the zero-value logic in the function body, sits in the
  declaration and never changes on any call; the type parameter itself is rebuilt at the use
  site on every call.
- Inference is read only from the arguments: if there is no argument (a return type alone), or
  if the arguments do not settle on the same type, the type parameter must be written
  explicitly at the call site.
- An unconstrained type parameter (`any`) allows, in the body, only operations valid for every
  type; operations such as `==` and `+` do not compile with an unconstrained `T`.
- All these measurements were made by asking the compiler itself: the error text was never
  read, only whether a source compiled or not was classified.

This boundary is not a shortcoming of generics, it is a natural consequence of their
definition: a generic function has to work, from a single source, for every `T`, and this is
possible only with operations independent of `T`'s concrete type. A constraint widens which
operations can be safely used, without breaking this independence.

#### Next Step

That `==` and `+` cannot be written with an unconstrained `T` may look like a shortcoming, but
it is actually the result of the compiler leaving everything it does **not know** about `T`
exactly as it is. The next lesson closes this ignorance with a declaration: a type constraint is
an interface that states **explicitly** in the source the set of types a type parameter accepts
and which operations that set guarantees — and this interface is used for a different job than
other interfaces.

### Type Constraints

Source: https://academia.sh/en/courses/go-type-system/type-constraints

A type constraint is an interface too, but it lists a type set, not a method: when the same three constraints are tested with four candidates, IntOnly accepts only int, UnderlyingInt (carrying ~int) also accepts named types whose underlying type is int, and the union ~int|~float64 named Numeric accepts both at once — 1/4, 2/4, 3/4. The limiting measurement shows that an interface listing a type set cannot be used as an ordinary value type, while an interface listing only a method can be both a constraint and a value type.

The Generic Functions lesson measured that an unconstrained type parameter is nearly useless in
the body: with `any`, neither `==` nor `+` compiled, only operations looking at the slice's own
structure, such as `len`, passed. That lesson left the mechanism filling this gap open with a
single sentence: a type constraint is an interface that states **explicitly** in the source the
set of types a type parameter accepts and the operations that set guarantees. This lesson opens
that sentence and asks the same question again: where does this constraint sit in the source,
and how do we know what it accepts?

The answer is short: the constraint sits in the **declaration**. In the common measurement, "the
constraint's declaration" is one of four declaration examples — alongside embedding, the
compile-time conformance proof, and a generic type's instantiation, one of the four bindings
written explicitly in the source. This lesson measures how that binding is written and what it
bounds: which types a constraint accepts, whether widening that acceptance has a cost, and where
else the constraint interface itself can be used.

#### A Constraint Lists a Type Set

**GE5.** An ordinary interface lists a **method set**: a type saying `interface{ Write() string
}` accepts every type that has a method named `Write`. A type constraint is also, syntactically,
an interface — it is written `interface{ ... }` — but instead of a method signature, **type
names** can be written inside the braces: `interface{ int | float64 }` defines a **type set**
that accepts only types that are `int` or `float64`. This is the same syntax used for two
separate jobs: one says "provide these methods," the other says "be one of these types."

The `~` mark widens this set. `~int` covers not only `int` itself, but every named type whose
**underlying type** is `int` — once `type Age int` is written, `Age` now falls into `~int`'s
set, but not into `int`'s set, because `Age` and `int` are not the same type, they only share
the same underlying type. The measurement below tests three constraints against four
candidates: `int` itself, a named type whose underlying type is `int` (`Age`), `float64`, and
`string`, which fits none of them.

```go
// constraint.go — how many types three constraints accept is counted by asking the compiler
package main

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

func compiles(source string) bool {
	dir, err := os.MkdirTemp("", "trial")
	if err != nil {
		return false
	}
	defer os.RemoveAll(dir)
	os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module trial\n\ngo 1.24\n"), 0o644)
	os.WriteFile(filepath.Join(dir, "code.go"), []byte(source), 0o644)
	cmd := exec.Command("go", "build", "./...")
	cmd.Dir = dir
	cmd.Env = append(os.Environ(), "GOTOOLCHAIN=local")
	return cmd.Run() == nil
}

type constraintDef struct{ name, body string }
type typeDef struct{ name, declaration, value string }

func main() {
	constraints := []constraintDef{
		{"IntOnly", "interface{ int }"},
		{"UnderlyingInt", "interface{ ~int }"},
		{"Numeric", "interface{ ~int | ~float64 }"},
	}
	types := []typeDef{
		{"int", "", "3"},
		{"Age (underlying int)", "type Age int\n", "Age(3)"},
		{"float64", "", "3.5"},
		{"string", "", `"x"`},
	}

	fmt.Printf("%-16s", "constraint")
	for _, t := range types {
		fmt.Printf("%-22s", t.name)
	}
	fmt.Println()

	for _, c := range constraints {
		fmt.Printf("%-16s", c.name)
		accepted := 0
		for _, t := range types {
			source := fmt.Sprintf("package trial\n%stype K %s\nfunc F[T K](a T) {}\nfunc G() { F(%s) }",
				t.declaration, c.body, t.value)
			ok := compiles(source)
			result := "no"
			if ok {
				accepted++
				result = "yes"
			}
			fmt.Printf("%-22s", result)
		}
		fmt.Printf("%d/4\n", accepted)
	}
}
```

```
constraint      int                   Age (underlying int)  float64               string                
IntOnly         yes                   no                    no                    no                    1/4
UnderlyingInt   yes                   yes                   no                    no                    2/4
Numeric         yes                   yes                   yes                   no                    3/4
```

**GE6.** The three rows, tested against the same four candidates, produce three different
acceptance counts based on how the constraint is written in the source. `IntOnly` accepts only
`int` itself — `Age` is rejected even though its underlying type is `int`, because it is a
separate type; this is the same rigidity as two arrays being counted as separate types when
their lengths differ (Arrays and Slices lesson, M09/K01). `UnderlyingInt` writes `~int`, so it
also accepts `Age`, and the acceptance count rises to two. `Numeric` lists the union (`|`) of
two `~` terms, so it covers both the integer and the floating-point side, rising to three —
`string` is rejected by all three, because no term covers it. This rise in the acceptance count
can be read directly from the source: `IntOnly`'s body has one term, `UnderlyingInt`'s has one
term but with a tilde, `Numeric`'s has a union of two terms. How wide a constraint is can be
stated in advance, with no call made at all, just by looking at the number of terms and the
tilde mark **in the constraint's own declaration**.

This measurement also shows where the binding surfaces when it breaks: when a caller tries to
instantiate a generic function with a type outside the constraint's set, the error surfaces not
at run time but **at compilation** — every "no" in the table is a compile rejection, proof that
trying to call a function constrained by `IntOnly` with a `string` argument ends not with a
panic but with compilation stopping.

The constraint itself also works with an implicit-satisfaction model: the `Age` type nowhere
says "I satisfy `UnderlyingInt`," not even the line `type Age int` mentions a constraint at
all. Whether a type falls into a constraint's set is determined from the outside, by looking not
at that type's own source but at its underlying type — the same mechanism the Interface Concept
lesson measured as implicit satisfaction, working here through type identity instead of
methods. The difference is here: to implicitly satisfy an interface, a type has to **write**
certain methods; to implicitly satisfy a constraint, a type does not need to write anything at
all — it is enough for its underlying type to match one of the terms the constraint lists. The
line defining the `Age` type could have been written before the constraint was even defined; once
the constraint came along later, `Age` automatically fell into its set, because membership looks
at `Age`'s underlying type, not its source.

The structure formed by terms separated with `|` is called a **union**: the type set a
constraint accepts is the union of each term's own set. `Numeric`'s set is the union of `~int`'s
set and `~float64`'s set; it is enough for a type to fall into **one** of these two subsets, it
does not need to fall into both. Union terms can be multiplied as much as desired (`~int |
~int8 | ~int16 | ~int32 | ~int64`), but every term only **widens** the set, no term ever takes
back an existing membership — this is why the table above shows a one-directional rise, from 1
to 2, from 2 to 3; no constraint accepts fewer types than the one before it.

As a constraint widens, the guarantee it gives narrows: `IntOnly` accepts only one type but
guarantees every `int` operation on it (including bit shifting); `Numeric` accepts three more
types but guarantees only the operations both families support in common (`+`, `-`, `*`, `/`,
comparison operators) — since bit shifting is not defined for `float64`, it cannot be written in
a body constrained by `Numeric`. The constraint's breadth in the source is inversely
proportional to the narrowness of operations the body can write; this is a more moderate version
of the extreme case the Generic Functions lesson showed with the `any` constraint (no operation
at all can be written).

#### `comparable`: The Built-in Constraint That Closes the Gap

The Generic Functions lesson's final measurement showed that a source writing `a == b` with an
unconstrained `T` does not compile. The reason can now be read from the source: `any` carries no
type-set term at all, it only says "every type is accepted," and gives no promise at all that
the `==` operator exists. The language closes this gap with its own built-in constraint:
`comparable` defines a type set covering every type that supports the `==` and `!=` operators —
except slice, map, and function types, because these three can never be compared with `==` in
Go.

```go
// closed.go — the comparable constraint closes the previous lesson's unconstrained == error
package main

import "fmt"

func Equal[T comparable](a, b T) bool { return a == b }

func main() {
	fmt.Println("Equal(3, 3)     :", Equal(3, 3))
	fmt.Println("Equal(\"x\", \"y\") :", Equal("x", "y"))
}
```

```
Equal(3, 3)     : true
Equal("x", "y") : false
```

**GE7.** `Equal` here has exactly the same body as the previous lesson's `Equal[T any]` writing —
the only difference is the constraint's name. This single word carries the information needed
for the `==` operator to be usable in the body; `comparable`, unlike `any`, is not part of the
standard library, it is a member of the language's own syntax, just like `any` itself. Where the
binding is built here is two-layered: **which operations** the constraint allows sits in the
constraint's own declaration (was `comparable` or `any` chosen), but **which concrete type**
that allowed operation will work for is again built at the call site, during instantiation.

#### The Limiting Measurement: A Constraint Interface Is Valid Only in Constraint Position

All three constraints above are interfaces. An ordinary interface can be used for two jobs: as a
variable's type (`var x Writer`) and as a type parameter's constraint (`func F[T Writer](...)`).
Whether a constraint listing a type set can do only one of these two jobs is the subject of the
measurement below.

```go
// limit2.go — a constraint interface cannot be used as a value type, measured by asking the compiler
package main

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

func compiles(source string) bool {
	dir, err := os.MkdirTemp("", "trial")
	if err != nil {
		return false
	}
	defer os.RemoveAll(dir)
	os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module trial\n\ngo 1.24\n"), 0o644)
	os.WriteFile(filepath.Join(dir, "code.go"), []byte(source), 0o644)
	cmd := exec.Command("go", "build", "./...")
	cmd.Dir = dir
	cmd.Env = append(os.Environ(), "GOTOOLCHAIN=local")
	return cmd.Run() == nil
}

func classifySource(source string) string {
	if compiles(source) {
		return "compiled"
	}
	return "did not compile"
}

func main() {
	numericConstraint := `package trial
type Numeric interface{ ~int | ~float64 }
func Add[T Numeric](a, b T) T { return a + b }`

	numericValue := `package trial
type Numeric interface{ ~int | ~float64 }
func F() { var x Numeric; _ = x }`

	writerConstraint := `package trial
type Writer interface{ Write() string }
func F[T Writer](a T) {}`

	writerValue := `package trial
type Writer interface{ Write() string }
func F() { var x Writer; _ = x }`

	fmt.Printf("%-38s %-13s %s\n", "interface", "constraint pos.", "value type")
	fmt.Printf("%-38s %-13s %s\n", "Numeric (lists a type set)",
		classifySource(numericConstraint), classifySource(numericValue))
	fmt.Printf("%-38s %-13s %s\n", "Writer (lists a method)",
		classifySource(writerConstraint), classifySource(writerValue))
}
```

```
interface                              constraint pos. value type
Numeric (lists a type set)             compiled      did not compile
Writer (lists a method)                compiled      compiled
```

**GE8.** `Numeric` compiles in a constraint position (`[T Numeric]`) but does not compile as an
ordinary variable type (`var x Numeric`): an interface carrying a type-set term, as far as the
source says, can exist only as a constraint. `Writer`, on the other hand, compiles in both:
a method-listing interface has no type-set restriction, so it can both carry a value and be a
constraint. The interface's two jobs split apart here: **contract** (which methods exist) and
**constraint** (which types are valid) are written with the same syntax, but the moment a
type-set term is added, the interface can do only the second job.

This distinction does not break the model the Interface Concept lesson built, it adds a boundary
to it: an interface's implicit satisfaction is still valid, but where the interface **itself**
can be used now depends on the terms in the interface's body. `Numeric` does not say what a type
can do, it says which types it **can be** — and that is a different question from a method
contract. The `Add[T Numeric]` function knows that `T` supports the `+` operator, because every
term in the constraint's type set (`~int`, `~float64`) supports that operator; but this
knowledge comes not from a method call, it comes from **set membership**.

In this lesson two places were placed side by side, and the difference was shown by running it
in both: the constraint itself sits in the declaration (how many terms, which tilde, which
union), but which concrete type the constraint applies to is again built at the use site, during
instantiation. The Generic Functions lesson measured this second place on its own; this lesson
added the declaration side to it and showed how the two work together — without a constraint the
use site allowed no operation at all; with a constraint, which operations are allowed can be
read in advance, from the source.

That these two jobs are written with the same syntax is a direct consequence of the constraint
concept not being a separate structure added to Go afterward: instead of opening a new keyword,
the language widened the existing interface syntax to also accept type terms. The cost of this
was seen in the measurement above — one end of the widened syntax (adding a type term) closes
the other end (using it as a value type). Whoever writes a constraint therefore has to think
about two questions together: how wide should the types the constraint accepts be, and will
this constraint ever need to be used as an ordinary value type one day. If the answer to the
second question is yes, the constraint should carry no type term — it should list only methods;
if the constraint exists only to bound type parameters, adding a type term has no cost, because
it is not expected to be used for anything else anyway.

#### Summary

- A type constraint is, syntactically, an interface, but it lists a **type set** instead of a
  method; which types are accepted is read from the constraint's own declaration.
- `~T` covers not only `T` itself but every named type whose underlying type is `T`; terms
  joined with `|` widen the set.
- When the same four candidates are tested against three constraints, the acceptance count rises
  with the number of terms in the constraint and the tilde mark: 1/4, 2/4, 3/4.
- When a constraint is violated — a caller instantiates with a type outside the set — the
  binding surfaces not at run time but at compilation.
- An interface carrying a type-set term is valid only in constraint position; a method-listing
  interface can be both a constraint and an ordinary value type.
- An interface's two jobs — contract and constraint — are written with the same syntax, but the
  moment a type-set term is added, the interface narrows to only the second job.

A constraint widens the narrowest boundary drawn by unconstrained `any`, but it does not remove
the boundary entirely: however widely a constraint is written, the body can still use only the
operations the constraint guarantees. This shows where the cost of type safety goes — the
safety comes from bounding, in advance, in the constraint's declaration, what the body is
allowed to write.

#### Next Step

A constraint bounds, in the declaration, which types a type parameter accepts; but the
constraint itself does not say where a **data structure** built with that type parameter will be
instantiated. The next lesson measures this: when a generic struct is instantiated twice, as
`Stack[int]` and `Stack[string]`, whose declaration — the writer's or the user's — does this
instantiation sit in, and what does that give type safety.

### Generic Data Structures

Source: https://academia.sh/en/courses/go-type-system/generic-data-structures

A generic data structure's type parameter is written explicitly in the caller's declaration: Stack[int] and Stack[string] show up in %T as two separate dynamic types, and one cannot substitute for the other. The measurable counterpart of type safety: using an element in the same structure written with the empty interface needs a type assertion, in the generic structure it needs zero. The limiting measurement shows that comparability depends both on the container's own field structure and on the type parameter itself.

The Type Constraints lesson showed that a binding is built in two layers: the operations a
constraint allows sit in the declaration, which concrete type those operations will work for is
built at the use site. This lesson measures the same two layers over a data structure, but with
one difference: a generic function's type parameter was **inferred** at the call site, it was
never written in the source at all. A generic data structure's type parameter, by contrast, is
written **explicitly** in the caller's own declaration — whoever writes `Stack[int]` places
`[int]` with their own hand, the compiler does not read it from an argument. Same question, two
different answers: the binding is still built outside the source (outside the function's own
definition), but this time not by inference, by a declaration the caller writes.

The source of this difference sits in the same place as the difference between a function call
and a type declaration. In a function call, arguments already exist — the compiler can look at
their type and infer `T`. In a type declaration, there is no argument yet at all; `Stack[int]`
is not a value, it is a **type name**, and there is no argument at all for the compiler to infer
which type parameter that type name is instantiated with. The compiler needs something to read
in order to infer; in a generic function this is the call's arguments, in a generic type there
is no such argument — the caller itself has to write it.

#### Instantiation Sits in the Caller's Declaration

A generic data structure is a type declaration that adds a type parameter list right after its
name: `type Stack[T any] struct{ items []T }`. This single source builds a **type-safe
container** pattern usable both for `Stack[int]` and for `Stack[string]` — but before it can be
used, it first has to be **instantiated**, and this instantiation is always written in the
using side's own declaration: in a variable definition, a field type, or a return type.

`Stack`'s methods do not redefine their own type parameter; the `(s *Stack[T])` receiver
expression **carries** the `T` already opened in the type's own declaration into the method.
`Push(v T)` and `Top() (T, bool)` therefore open no new square brackets — the `T` they use is
already bound to `int` the moment `Stack[int]` is instantiated, together with `Stack` itself.
Instantiation happens once, in the type's own declaration; every method bound to that type
shares this single instantiation.

```go
// stack.go — the instantiation of a generic data structure sits in the caller's declaration
package main

import "fmt"

type Stack[T any] struct{ items []T }

func (s *Stack[T]) Push(v T) { s.items = append(s.items, v) }

func (s *Stack[T]) Top() (T, bool) {
	var zero T
	if len(s.items) == 0 {
		return zero, false
	}
	return s.items[len(s.items)-1], true
}

func main() {
	var intStack Stack[int]
	intStack.Push(3)
	intStack.Push(5)

	var stringStack Stack[string]
	stringStack.Push("apple")

	i, _ := intStack.Top()
	s, _ := stringStack.Top()

	fmt.Printf("%-24s type=%-16T value=%v\n", "intStack", intStack, i)
	fmt.Printf("%-24s type=%-16T value=%v\n", "stringStack", stringStack, s)
	fmt.Println("arithmetic on the result:", i+1)
}
```

```
intStack                 type=main.Stack[int]  value=5
stringStack              type=main.Stack[string] value=apple
arithmetic on the result: 6
```

**GE9.** The line `var intStack Stack[int]` is itself a **declaration** stating that `T` will be
`int` — compared with the `firstElement([]int{7, 2, 9})` call from the Generic Functions
lesson, the difference is plain: there, what `T` was got **inferred** from the argument's type,
and nowhere in the source did `int` ever appear written at all; here, `int` sits right in the
line, visible to the eye. `%T` confirms this: `intStack` and `stringStack` were produced from
the same source, but their dynamic types at run time are `main.Stack[int]` and
`main.Stack[string]` — two entirely separate types, just as `[4]int` and `[3]int` are separate
types (Arrays and Slices, M09/K01). In the common measurement, this binding counts as one of the
declaration examples under the name "instantiation of a generic type."

It is also worth noting that the line `var intStack Stack[int]` contains no call at all — this
is a zero-value declaration; no `New`-like constructor is called. As the Zero Values lesson
(M09/K01) measured, a struct's zero value is built from its own fields' zero values: the
`items` field here starts as a `nil` slice. This shows the difference between instantiation and
being given an initial value: writing `Stack[int]` binds `T`, but it does not fill `items`; the
first `Push` call runs `append` on a `nil` slice, and as the Variadic Functions lesson
(M09/K01) measured, this works without a problem — appending to a `nil` slice grows it, it does
not panic.

#### The Measurable Counterpart of Type Safety

The two instantiations being separate types has a consequence: one cannot **substitute** for the
other. The measurement below counts this, and the cost of writing the same job with the empty
interface, by asking the compiler.

```go
// safety.go — the measurable counterpart of type safety: how many type assertions are needed
package main

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

func compiles(source string) bool {
	dir, err := os.MkdirTemp("", "trial")
	if err != nil {
		return false
	}
	defer os.RemoveAll(dir)
	os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module trial\n\ngo 1.24\n"), 0o644)
	os.WriteFile(filepath.Join(dir, "code.go"), []byte(source), 0o644)
	cmd := exec.Command("go", "build", "./...")
	cmd.Dir = dir
	cmd.Env = append(os.Environ(), "GOTOOLCHAIN=local")
	return cmd.Run() == nil
}

func classifySource(source string) string {
	if compiles(source) {
		return "compiled"
	}
	return "did not compile"
}

func main() {
	substitution := `package trial
type Stack[T any] struct{ items []T }
func F() {
	var a Stack[int]
	var b Stack[string]
	b = a
	_ = b
}`

	anyNoAssertion := `package trial
type AnyStack struct{ items []any }
func (s *AnyStack) Push(v any) { s.items = append(s.items, v) }
func (s *AnyStack) Top() any { return s.items[len(s.items)-1] }
func F() {
	var s AnyStack
	s.Push(3)
	t := s.Top()
	_ = t + 1
}`

	anyWithAssertion := `package trial
type AnyStack struct{ items []any }
func (s *AnyStack) Push(v any) { s.items = append(s.items, v) }
func (s *AnyStack) Top() any { return s.items[len(s.items)-1] }
func F() {
	var s AnyStack
	s.Push(3)
	t, ok := s.Top().(int)
	_ = ok
	_ = t + 1
}`

	genericNoAssertion := `package trial
type Stack[T any] struct{ items []T }
func (s *Stack[T]) Push(v T) { s.items = append(s.items, v) }
func (s *Stack[T]) Top() T { return s.items[len(s.items)-1] }
func F() {
	var s Stack[int]
	s.Push(3)
	t := s.Top()
	_ = t + 1
}`

	fmt.Printf("%-50s %s\n", "case", "result")
	fmt.Printf("%-50s %s\n", "Stack[string] = Stack[int] (substitution)", classifySource(substitution))
	fmt.Printf("%-50s %s\n", "any stack, arithmetic with 0 type assertions", classifySource(anyNoAssertion))
	fmt.Printf("%-50s %s\n", "any stack, arithmetic with 1 type assertion", classifySource(anyWithAssertion))
	fmt.Printf("%-50s %s\n", "generic stack, arithmetic with 0 type assertions", classifySource(genericNoAssertion))
}
```

```
case                                               result
Stack[string] = Stack[int] (substitution)          did not compile
any stack, arithmetic with 0 type assertions       did not compile
any stack, arithmetic with 1 type assertion        compiled
generic stack, arithmetic with 0 type assertions   compiled
```

**GE10.** The first line directly shows that the two instantiations cannot substitute for each
other: `b = a` does not compile, because `Stack[string]` and `Stack[int]` are, to the compiler,
as separate as `[4]int` and `[3]int`. This is the direct consequence of instantiation sitting in
the caller's declaration — the type written in the declaration becomes part of that variable's
permanent identity.

**GE11.** The last three lines compare the same job written two ways. `AnyStack`, written with
the empty interface, returns `any` from `Top()`; trying to add `+1` directly to this value does
not compile, because `any`'s concrete type is not known in the source. The only way out is
writing a **type assertion** (`s.Top().(int)`) — the same mechanism the Type Assertion and Type
Switch lesson measured, appearing here as a burden that has to be written over and over at every
read point of a container. The generic `Stack[int]`, by contrast, returns `int` directly from
`Top()`, and `+1` compiles with no type assertion written at all — this is the measurable
counterpart of type safety: the number of type assertions needed to do the same job is at least
one in the `any` writing, zero in the generic writing.

This difference compounds as the container grows. Every read point of `AnyStack` — `Top`, the
return of a `Bottom` method if one were added, every element of a slice a `Contents` method
might return — has to carry its own type assertion, because the container hides type
information behind `any`, and every reader has to recover this information on its own. In
`Stack[T]`, type information is part of the container's own type; no matter how many new
methods are added, none of them ever has to write a type assertion, because their return types
are already `T` — a concrete type known at compile time. A type assertion is not free either:
if a value of the wrong type was added (which is possible for an `any` container with no
compile error at all), the assertion either panics at run time or silently fails in its
two-value form — in the generic writing this entire class of error never exists at all, because
adding an element of the wrong type is already rejected at compilation.

#### The Limiting Measurement: Comparability Depends on Both the Structure and T

Type safety is not free. Whether a generic type can be compared with `==` depends not only on
the type parameter, but also on the container's **own field structure** — and these two factors
work independently.

```go
// comparison.go — a generic type's comparability depends on both its own field structure and T
package main

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

func compiles(source string) bool {
	dir, err := os.MkdirTemp("", "trial")
	if err != nil {
		return false
	}
	defer os.RemoveAll(dir)
	os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module trial\n\ngo 1.24\n"), 0o644)
	os.WriteFile(filepath.Join(dir, "code.go"), []byte(source), 0o644)
	cmd := exec.Command("go", "build", "./...")
	cmd.Dir = dir
	cmd.Env = append(os.Environ(), "GOTOOLCHAIN=local")
	return cmd.Run() == nil
}

func classifySource(source string) string {
	if compiles(source) {
		return "compiled"
	}
	return "did not compile"
}

func main() {
	stackWithSlice := `package trial
type Stack[T any] struct{ items []T }
func F() bool { var a, b Stack[int]; return a == b }`

	boxComparableT := `package trial
type Box[T any] struct{ Value T }
func F() bool { var a, b Box[int]; return a == b }`

	boxIncomparableT := `package trial
type Box[T any] struct{ Value T }
func F() bool { var a, b Box[[]string]; return a == b }`

	fmt.Printf("%-48s %s\n", "case", "result")
	fmt.Printf("%-48s %s\n", "Stack[int] == Stack[int] (slice field)", classifySource(stackWithSlice))
	fmt.Printf("%-48s %s\n", "Box[int] == Box[int] (T direct, T=int)", classifySource(boxComparableT))
	fmt.Printf("%-48s %s\n", "Box[[]string] == Box[[]string] (T=slice)", classifySource(boxIncomparableT))
}
```

```
case                                             result
Stack[int] == Stack[int] (slice field)           did not compile
Box[int] == Box[int] (T direct, T=int)           compiled
Box[[]string] == Box[[]string] (T=slice)         did not compile
```

**GE12.** The three lines separate two independent factors. `Stack[T]` can never be compared —
even when `T` is `int` — because `items` is a **slice** field, and as the Maps and Arrays and
Slices lessons (M09/K01) measured, slices cannot be compared with `==`; this restriction comes
from the container's own field structure, not from the type parameter. `Box[T]` builds a
different structure — its `Value` field holds `T` directly, not a slice — and here comparability
is handed entirely over to `T`: `Box[int]` is comparable because `int` is comparable,
`Box[[]string]` is not comparable because this time `T` itself (`[]string`) is a type that
cannot be compared. A generic type's comparability therefore cannot be reduced to a single
question; it requires looking at both the container's own design and the `T` it is filled with.

This also shows why constraints are not enough on their own: turning the `Stack[T any]`
constraint into `Stack[T comparable]` would work for `Box`, but changes nothing at all for
`Stack` — however comparable `T` is, as long as `items` is a slice, `Stack` remains
uncomparable. A type constraint bounds only `T`, not the field layout of the type carrying `T`.

This distinction has a concrete counterpart: the Maps lesson (M09/K01) measured that a map's key
type must be comparable. `Stack[int]` can never be a map key — no matter what `T` is — because
the incomparability comes from the `items` field, and this field stays exactly as it is at every
instantiation. `Box[int]`, on the other hand, can be a key, because its `Value` field holds `int`
directly, and `int` is comparable; `Box[[]string]` cannot, because this time the `T` it carries
is itself incomparable. The same two container patterns answer, in advance, the question "can
this type be a map key" just by looking at the field structure in the source — with no need at
all to try a key at run time.

Across three lessons, genericity stayed inside the source the whole time: the type parameter was
either inferred or written explicitly, but in both cases, someone reading the source, looking in
the right place, could see which concrete type was being used. This is the exact opposite
posture of the implicit satisfaction the Interface Concept lesson measured: there, a type
satisfying an interface was never written anywhere at all; here, which type a container works
with can **always** be read either from the call's argument or from the declaration's square
brackets. `Stack[int]`'s dynamic type can be known without ever asking `%T` at all, because it is
written right in the source itself; an interface value's dynamic type, by contrast, could only
be learned by asking the value. The course's closing lesson places these two postures — always
readable versus learnable only by asking the value — side by side by doing the same job.

This readability is not free: someone reading `Stack[int]` learns that `T` is `int` from the
line itself, but this information cannot be inferred anywhere else — like from an argument, as
in the Generic Functions lesson; it is not possible for the writer to forget to write it,
because the syntax makes it mandatory. The predictability genericity gives comes with this small
writing burden the caller takes on.

#### Summary

- A generic data structure's type parameter, unlike a generic function's, is written explicitly
  in the caller's own declaration — it is never inferred.
- `Stack[int]` and `Stack[string]`, even though produced from the same source, are two separate
  dynamic types read at run time with `%T`, and one cannot substitute for the other.
- The measurable counterpart of type safety: writing the same job with the empty interface
  requires at least one type assertion, in the generic writing this count is zero.
- A generic type's comparability depends on two independent factors: the container's own field
  structure (if it carries a slice field, it can never be compared) and — if the container holds
  `T` directly — `T`'s own comparability.
- A type constraint bounds only the type parameter; it cannot change the limits coming from the
  container's own field layout.

#### Next Step

Across three lessons, genericity was always measured on one side: the binding was built either
in the declaration or at the use site, and it never entered the value. The interface topic
measured the exact opposite — type information was carried inside the value and could only be
read by asking the value. The course's final lesson places these two writings, and the interface
contract standing between them, side by side by doing the same job three times: how many type
assertions each writing requires, which one stays at compile time and which one falls to run
time, and whether genericity always wins.

### When Generics

Source: https://academia.sh/en/courses/go-type-system/when-generics

The same total-area computation is built three ways: with the empty interface, three type assertions; with generics, zero, but T is pinned to a single concrete type; with an interface contract, also zero, but different concrete types can sit in the same slice. The limiting measurement shows that once T is pinned to an interface (to accept a heterogeneous collection), generics' gain disappears: element size rises from 8 bytes to 16 bytes, exactly like an ordinary interface value. The course found 4 of sixteen bindings in the declaration, 4 at the use site, 8 inside the value.

The Generic Data Structures lesson showed that genericity stayed inside the source across three
lessons: the type parameter was either inferred or written explicitly in the declaration, but it
never entered the value. The interfaces topic measured the exact opposite — the dynamic type was
carried inside the value and could only be read by asking the value. This closing lesson places
these two writings, and the interface contract standing between them, side by side by **doing
the same job three times**: a total-area computation is built with the empty interface, with
generics, and with an interface contract, and in all three, where the binding is built is
counted.

This was the course's third claim: generics and interfaces do the same job in two separate
places. In generics the binding is built in the declaration and at the use site, it never enters
the value; in an interface the binding enters the value. This lesson measures this difference for
the first time by directly running it, writing the same computation three times.

The previous three lessons always measured generics on their own — never placed side by side
with an interface. This left a debt in the course's general framework: the claim "in generics
the binding never enters the value" was, without a comparison being made, only an observation.
This lesson closes that debt: the same job is written with both generics and an interface and
measured in the same run, and the difference turns from a claim into a number (8 bytes against
16 bytes).

#### The Same Job, Three Writings

The job measured is simple: computing the total area of shapes in a collection. `Square` and
`Circle` each carry their own `Area()` method; the `Shape` interface lists this single method.
This job was chosen deliberately: the area computation **genuinely varies** by type — a square's
area is the side squared, a circle's area is the radius squared times a constant — but the
summing operation itself is the same for every type. The job all three writings have to
accomplish therefore splits into two parts: the type-specific part (`Area()`'s own body) and the
type-independent part (accumulating the total).

```go
// three.go — the same job in three writings: empty interface, generics, interface contract
package main

import (
	"fmt"
	"unsafe"
)

type Square struct{ Side float64 }

func (s Square) Area() float64 { return s.Side * s.Side }

type Circle struct{ Radius float64 }

func (c Circle) Area() float64 { return 3.14159 * c.Radius * c.Radius }

type Shape interface{ Area() float64 }

// 1) with the empty interface: a type assertion (a type-switch branch) is needed for every element.
func totalAreaAny(s []any) (total float64, assertionCount int) {
	for _, e := range s {
		switch v := e.(type) {
		case Square:
			assertionCount++
			total += v.Area()
		case Circle:
			assertionCount++
			total += v.Area()
		}
	}
	return
}

// 2) with generics: the body is the same for every T, and as long as T stays homogeneous there is no type assertion at all.
func totalAreaGeneric[T Shape](s []T) (total float64) {
	for _, e := range s {
		total += e.Area()
	}
	return
}

// 3) with an interface contract: different concrete types in the same slice, no type assertion at all.
func totalAreaInterface(s []Shape) (total float64) {
	for _, e := range s {
		total += e.Area()
	}
	return
}

func main() {
	fmt.Println("-- same job, three writings --")

	mixed := []any{Square{Side: 2}, Circle{Radius: 1}, Square{Side: 3}}
	tAny, assertions := totalAreaAny(mixed)
	fmt.Printf("empty interface  total=%.4f  type assertions=%d  declaration lines=0\n", tAny, assertions)

	homogeneous := []Square{{Side: 2}, {Side: 3}}
	tGeneric := totalAreaGeneric(homogeneous)
	fmt.Printf("generic          total=%.4f  type assertions=0  declaration lines=1 (constraint)\n", tGeneric)

	contracted := []Shape{Square{Side: 2}, Circle{Radius: 1}, Square{Side: 3}}
	tInterface := totalAreaInterface(contracted)
	fmt.Printf("interface        total=%.4f  type assertions=0  declaration lines=1 (interface)\n", tInterface)

	fmt.Println()
	fmt.Println("-- limiting measurement: the gain disappears once T is pinned to an interface --")
	heterogeneous := []Shape{Square{Side: 2}, Circle{Radius: 1}}
	tDegenerate := totalAreaGeneric(heterogeneous)
	fmt.Printf("totalAreaGeneric[Square](homogeneous)   T=Square  element size=%d bytes  result=%.4f\n",
		unsafe.Sizeof(homogeneous[0]), tGeneric)
	fmt.Printf("totalAreaGeneric[Shape](heterogeneous)  T=Shape   element size=%d bytes  result=%.4f\n",
		unsafe.Sizeof(heterogeneous[0]), tDegenerate)
}
```

```
-- same job, three writings --
empty interface  total=16.1416  type assertions=3  declaration lines=0
generic          total=13.0000  type assertions=0  declaration lines=1 (constraint)
interface        total=16.1416  type assertions=0  declaration lines=1 (interface)

-- limiting measurement: the gain disappears once T is pinned to an interface --
totalAreaGeneric[Square](homogeneous)   T=Square  element size=8 bytes  result=13.0000
totalAreaGeneric[Shape](heterogeneous)  T=Shape   element size=16 bytes  result=7.1416
```

**GE13.** The first three lines compare three sources of the same computation. The empty
interface writing runs a type assertion (a branch of the type switch) for all three of its three
elements and declares no interface at all — the contract is written nowhere in the source, only
the `case` branches inside the `switch` implicitly say which types are expected. The generic
writing runs with zero type assertions, but `homogeneous` contains only `Square` — `T`, as the
Generic Data Structures lesson measured, is pinned to a single concrete type in the caller's own
declaration. The interface writing also runs with zero type assertions, and moreover can carry
`Square` and `Circle` in the **same slice** — `contracted` holds both at once, which
`homogeneous` could not have held.

#### The Limiting Measurement: The Gain Disappears Once T Is Pinned to an Interface

**GE14.** The last two lines show the limit of genericity. Because `totalAreaGeneric`'s
constraint is a method list, it can also instantiate `T` with an interface type —
`totalAreaGeneric[Shape](heterogeneous)` compiles and accepts `Square` and `Circle` in a mixed
slice. But its cost is measured: with `T=Square`, every element is 8 bytes (just the `Side`
field itself); with `T=Shape`, every element is 16 bytes — the sum of the type pointer and the
data pointer an interface pair carries. Pinning `T` to an interface breaks genericity's promise
of zero cost: the code is now running something no different at all from `totalAreaInterface`, it
has just hidden this behind a type parameter.

`totalAreaGeneric`'s body stayed letter-for-letter the same across both calls — the line `for _,
e := range s { total += e.Area() }` changed for neither `T=Square` nor `T=Shape`. What changed
was only what `T` was, and this change doubled the element size. This comes from the very
definition of genericity: a generic body, from a single source, runs the **same** operation for
every `T` it is instantiated with. With `T=Square`, this operation calls `Square.Area()`
directly, with no extra layer at all; with `T=Shape`, the same operation now makes an indirect
method call through an interface value — even though not a single line changed in the source,
what happens at run time changes.

This gives a definite answer to the course's question: generics do not always win. If a
collection's elements are all the same concrete type, and the operation performed on them is the
same for every type, generics provide both zero type assertions and zero extra bytes. If the
elements have to carry different concrete types — if behavior genuinely has to vary by type —
two choices remain: pin `T` to an interface and give up genericity's gain, or write an interface
from the start. The second is the more honest writing, because the source already says what is
going to happen — that more than one concrete type will sit in the same place. A generic body,
by definition, stays the **same** for every `T` it is instantiated with; wanting it to behave
differently for different `T`s contradicts genericity itself.

#### Course Wrap-Up

Interfaces, Errors and Generics opened with a single question: in Go, a type satisfying an
interface is never written anywhere at all — so where is this binding built? Thirteen lessons
asked this question across three mechanisms: in interfaces (implicit satisfaction, the empty
interface, type assertion, embedding, consumer-side definition), in error values (the error
interface, sentinel errors, wrapping, panic), and in generics (the type parameter, the type
constraint, generic data structures, and this lesson's comparison of all three).

The first claim was confirmed: eight of sixteen bindings are carried inside the value, because a
Go interface value is a pair — both a concrete type and data. The second claim was confirmed:
four bindings are built at the use site, and the source never says anywhere at all that a type
satisfies an interface. The third claim was run directly in this lesson: generics and interfaces
do the same job in two separate places — in generics the binding stays in the declaration and at
the use site, in an interface it enters the value — and this gain of generics disappears the
moment the type parameter is pinned to an interface.

One of these three mechanisms, "use site," carries a warning: the use site is where the compiler
looks, not where the source points. The Generic Functions lesson proved this by asking the
compiler directly — writing the source to a temporary package and reading only whether it
passed; the Generic Data Structures and Type Constraints lessons used the same method. This
difference between an indirect proof (only showing that a contract is not written in the source)
and a direct proof (seeing, by running it, where the compiler stops) is part of the course's own
measurement discipline: if a lesson says "this is built at the use site," it must either call the
compiler and show where it stops, or explicitly write that the proof is indirect.

The course closes without overgeneralizing: these three mechanisms (declaration, use site, value)
do not cover all of Go's type system, they only classify the answers given to the single
question these thirteen lessons asked — where is this binding built? The table below gathers
these thirteen answers into a single row each.

| Lesson | Binding measured | Where it is built (declaration · use site · value) | Limiting measurement |
|---|---|---|---|
| The Interface Concept | A type implicitly satisfying an interface; the small-interface principle | use site (the 4/4/8 distribution was produced here) | The compile-time conformance proof carries the binding into the declaration; without the proof, the compiler stays silent |
| The Empty Interface and `any` | The empty interface carrying type information into the value | value | An interface holding a `nil` pointer is not `nil` |
| Type Assertion and Type Switch | Asking the value: the two-value assertion, the branch a type switch selects | value | The same value can fit both a concrete and an interface branch; the branch chosen depends on write order |
| Interface Embedding | Contract composition | declaration | Embedding merges only the contract, not the implementation |
| Interface Design | The same concrete type satisfying four independently defined consumer interfaces; zero references in its own source | use site | Two separately named interfaces with the same signature become implicitly equivalent; equivalence breaks only once the signature diverges |
| The Error Interface | An error being an implicitly satisfied value | use site + value | A non-`nil` interface can carry a `nil` concrete value |
| Sentinel Errors | Error identity sitting in the value | value | Once identity changes, the caller silently falls into the wrong branch, producing no warning |
| Error Wrapping and Unwrapping | The chain being carried in the value | value | Using `%v` breaks the chain, the text stays the same |
| Panic and Recovery | A panic leaving no trace at all in the signature | (no binding is built) | Recovering does not destroy the panic, it carries it; if the recovered value is not written down, the information is lost entirely |
| Generic Functions | A type parameter inferred at the call site | use site | `==` and `+` cannot be written with an unconstrained `T` |
| Type Constraints | A constraint listing a type set | declaration | An interface with a type-set term is valid only in constraint position |
| Generic Data Structures | Instantiation sitting in the caller's declaration | declaration | Comparability also depends on the container's own field structure |
| When Generics | Where the same job binds across three writings | (comparison of all three) | Once `T` is pinned to an interface, generics' gain disappears |

The three mechanisms measured across the course ultimately reduce to a single habit: asking,
while reading a source, "where is this binding built?" If a declaration line is searched for and
found, the binding is there; if not, two places remain — either a binding the compiler builds at
the use site (the only way to see this is to call the compiler), or a binding that can only be
learned by asking the running value. Asking these three questions in order is a measurable way
of separating what Go's type system says in the source from what it does not, while reading it.

#### Summary

- The same total-area computation was built three ways: the empty interface required three type
  assertions, generics and the interface ran with zero type assertions.
- Generics stay zero-cost only when `T` is pinned to a single concrete type; once `T` is pinned
  to an interface, element size rises from 8 bytes to 16 bytes and the gain disappears.
- An interface is needed when behavior genuinely has to vary by type, because a generic body
  stays the same for every `T` it is instantiated with.
- Of the sixteen bindings measured across the course, four were built in the declaration, four
  at the use site, eight inside the value; this distribution classifies not Go's type system,
  only the question these thirteen lessons asked.
- Generics and interfaces do the same job in two separate places: in generics the binding never
  enters the value at all, in an interface it does — and this difference is the measurable
  counterpart of when to choose which.

