---
title: 'Go Fundamentals'
source: 'https://academia.sh/en/courses/go-fundamentals'
language: en
updated: '2026-09-13T07:46:26+00:00'
license: 'CC BY-SA 4.0'
lessons: 18
outcomes:
    - "Writes programs with Go's type system and zero value behavior"
    - 'Uses slice and map structures together with their costs'
    - 'Applies package layout and the build commands'
    - 'Separates by measuring whether an unwritten thing is answered by the language, by the compiler or left open'
---

# Go Fundamentals

The most useful question when reading a Go program is not what the written code does but what the unwritten code would have been: the course measures every mechanism with a single question — when you do not write this, does the language supply something defined, does the compiler reject it, or is the outcome left open? Zero values, slices, maps, pointers, package layout and the command family all pass through the same frame.

## Language Basics

Tying the language's design goals to readability and compile speed, running the first program with module initialization, short declaration alongside sequential constants, the distinction between integer and floating-point types, the defined behavior of every variable left without an initial value, control flow and loop forms gathering into a single keyword, and what a multiple return value and a variadic argument correspond to in an empty call.

### Go's Design Goals

Source: https://academia.sh/en/courses/go-fundamentals/design-goals

Four things the Go compiler rejects — an unused variable, an unused import, an unwritten return value, an implicit type conversion — are run from inside the program by calling a compiler; all four stop compilation, and rejecting the implicit conversion grows the source doing the same work from 15 characters to 22.

M08's six courses asked the same platform six separate questions; the last one measured a
behavior that in the source might trigger from no call at all — a provider present on the
class path could activate without a single line written in the source. This course walks
through the same door but moves the question from the platform to **the language itself**: a
language decides for itself what stands in for the thing you did not write. In some places
that stand-in is a defined value; in others the language refuses to recognize the unwritten
state at all and rejects the source outright.

This first lesson measures rejection. The Go compiler refuses to accept four forms that would
never cause a problem at runtime: an unused variable, an unused import, an unwritten return
value, an implicit type conversion. All four are, in most other languages, mostly a warning;
here the source never turns into a class file or an executable at all. The question asked is:
when one of these is left unwritten, what does the compiler do, and is this rejection really a
restriction, or does it just move what needed to be written somewhere else?

The rest of the course will keep asking the same triple question: does an unwritten thing find
a **defined** counterpart in the language, is it **rejected** by the compiler, or is the
outcome left to runtime and **open**? This first lesson takes up only the middle of the three
answers — rejection — because rejection draws the sharpest line around a language's
boundaries: when a language does not allow something, there is no need to even discuss how
that thing would have behaved.

#### The Chain and This Lesson's Share

The Programming Foundations course (M01/K02) built variable definition, operators, and
explicit versus implicit conversion using Python; there, implicit conversion was mostly a
runtime behavior that passed without incident. **That concept is not rebuilt here** — one
sentence is enough to state the difference: in Go the same implicitness is not a runtime event
but a **compile-time rejection**. The How Computers Work course (M01/K01) built the
compiler–interpreter distinction as a concept; in this lesson the compiler is not a concept but
**a tool called from inside a program**, and the only answer it gives is whether the source
passed or not.

The Data Structures course (M01/K03) built the array, the list, and the map as abstract data
types and compared them by complexity; this curriculum does not repeat that comparison. M09's
share is **which mechanism in Go** realizes those already-built concepts — in this first
lesson the mechanism is not yet a data type, it is the stance the compiler takes toward the
source. The lessons that follow will carry this mechanism into variables, types, and control
flow in turn.

#### Three Design Goals, One Measurable Outcome

Three of Go's design goals are cited most often: simplicity, compile speed, readability. This
lesson does not run all three directly — duration is not a unit of measurement, because how
long a compilation takes depends on the machine and the load at that moment and varies from
one run to the next. But there is one place where all three leave a **visible trace** in the
source: what the compiler rejects. If a language accepts every construct, the reader has to
infer from the source which path was intended; if a language rejects a construct, the party
writing it is forced to **carry** their intent into the source. Simplicity here is not "writing
fewer characters" but the smallness of the number of forms the language accepts: the more ways
there are to do one task, the more ways the compiler has to reject. Compile speed comes from
these rejection decisions **not depending on file order** — an unused import is visible while
that file alone is read, without waiting on how another file compiles.
Readability is what this lesson measures directly: all four rejections exist so that whoever
reads the source can read every name and every type **exactly as written**.

These three goals can appear to conflict with each other — writing less and writing intent
explicitly do not point the same way. The rest of this lesson measures that tension: do the
four rejected forms really block a shorter way of writing, or do they make visible something
that could have been left implicit?

#### The Compiler Is Called From Inside the Program

The whole measurement comes down to a single question: does a piece of source text compile?
We answer that question without asking an external tool, using only the standard library's
ability to start a process and write files. The source text is written into a temporary
directory together with a module file, the compiler is run in that directory, and only the
exit status is read.

```go
// compiler.go — helper that calls the compiler from inside the program
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
}

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

**LB1.** The compiler here is not a concept but a call: `compiles` writes the source into a
temporary package, runs the compiler as a subprocess, and reads only the exit status. The
error text is never read, because the shape of an error message is version-dependent data; what
is measured through the entire course is not "why it was rejected" but "was it rejected".

This distinction has a cost: since `compiles` returns only `true` or `false`, a source could
have been rejected for a reason **other** than the one intended, and the measurement cannot
tell the difference. LB2 closes off that risk — each of the four sources is hand-built and
verified to compile if not for exactly one unwritten thing. This is a limit worth keeping in
mind while reading the four-line table shown in the lesson: `compiled`/`not compiled` is not a
debugging tool, it is a classification tool.

**LB2.** Each of the following four sources compiles if not for exactly one unwritten thing.
The rejection comes from that single cause — not from some other syntax error. This condition
holds throughout this lesson and the rest of the course wherever a rejection is measured.

Not needing an external tool secures two things at once. First, the measurement is
**deterministic**: the same source text gives the same answer every run, because the compiler
itself is called, not a separate interpreter mimicking it. Second, the measurement is
**portable**: `compiles` only uses the standard library's process-starting and
temporary-file-writing ability, and the rest of the course repeats this pattern.

#### Four Rejections, Four Reasons

The following four sources are run side by side in the same frame. Each carries a gap that
would never cause a problem at runtime — none of them is an infinite loop, a memory bound
overrun, or a type mismatch. Yet none of the four compiles.

```go
// main.go — four rejection reasons run side by side
package main

import "fmt"

func main() {
	fmt.Printf("%-32s %s\n", "reason", "result")
	fmt.Printf("%-32s %s\n", "unused variable", classifySource(`package trial
func F() { x := 1 }`))
	fmt.Printf("%-32s %s\n", "unused import", classifySource(`package trial
import "fmt"
func F() {}`))
	fmt.Printf("%-32s %s\n", "unwritten return value", classifySource(`package trial
func F() int { }`))
	fmt.Printf("%-32s %s\n", "implicit type conversion", classifySource(`package trial
func F() { var a int32 = 1; var b int64 = a; _ = b }`))
}
```

```
reason                           result
unused variable                  not compiled
unused import                    not compiled
unwritten return value           not compiled
implicit type conversion         not compiled
```

All four lines give the same result, but they do not come from the same reason. **Unused
variable** and **unused import** reject a case where something written in the source is never
read at all — the language does not recognize a "maybe I will use it later" middle ground; a
name is either used or it is never written in the source at all. **The unwritten return value**
belongs to a different class: the signature promised an `int`, but the body produces no value,
and the compiler does not accept a body that fails to keep the promise. **The implicit type
conversion** is a third class: an `int32` cannot be assigned directly to an `int64` variable —
because the target's bit width differs from the source's, the compiler requires the widening
to be **written**.

What the three share is that none of them would lead to an observable error at runtime. An
unused variable does not crash the program, an unused import breaks nothing, widening from
`int32` to `int64` loses no data. The compiler is rejecting these not based on runtime risk but
based on **the source's readability**: an unused name misleads the reader, an implicit widening
hides from the reader which type stands where. The rejection here is not a safety measure, it
is a decision to **narrow** what stands in for the unwritten — the language chooses, in these
four cases, "never allow it" over "put something defined there".

Two of the rejections (unused variable, unused import) share one common escape route: the
language's **blank identifier**, `_`. Assigning a value to `_` is a written way of saying "I
know this value but I will not use it"; the compiler counts this as use and does not reject it.
This looks at first like a loophole that punches through the rejection rule, but it actually
does the opposite: writing `_` erases the difference between an unused name and one **silently
left as is**, because both become equally visible in the source — one a real name, the other a
deliberate "I do not care about this" marker. This identifier will reappear later in the
course: when skipping one of several return values, and when asking whether a key exists in a
map, the same word is used again.

Each rejection captures a concrete situation that arises during a fix. An unused import is a
line left behind when code is moved and a call is deleted; if the compiler let it stand, the
source would fill up over time with dependencies no longer needed, and seeing which import is
truly required would grow harder. An unused variable carries a similar trace: an intermediate
result bound to a name and then left unused is most often the mark of a half-finished edit. The
unwritten return value comes from a different source — the signature is a promise, the body
must keep it; the compiler enforces this to protect the assumption the caller built while
reading the signature. The implicit type conversion is the most indirect of all: even if two
integer types are in the same family, if their bit widths differ, silently copying one value
into the other means, from the reader's perspective, a widening that **never happened**; the
compiler prefers to make that widening visible rather than let it pass silently.

#### Limiting Measurement: Rejecting Does Not Mean Writing Less

It would be misleading to think the rejection decision comes free. Take the rejection of the
implicit conversion: the line `var b int64 = a` does not compile when `a` is an `int32`. The
line that does the same job and does compile is `var b int64 = int64(a)` — the conversion call
**has to be written**. Compare the two lines directly.

```go
// limiter.go — rejecting does not mean writing less
package main

import "fmt"

func main() {
	implicitLine := "var b int64 = a"
	explicitLine := "var b int64 = int64(a)"

	implicitSource := "package trial\nfunc F() { var a int32 = 1; " + implicitLine + "; _ = b }"
	explicitSource := "package trial\nfunc F() { var a int32 = 1; " + explicitLine + "; _ = b }"

	fmt.Printf("%-38s %-13s length=%d\n", "implicit conversion", classifySource(implicitSource), len(implicitLine))
	fmt.Printf("%-38s %-13s length=%d\n", "explicit conversion", classifySource(explicitSource), len(explicitLine))
}
```

```
implicit conversion                    not compiled  length=15
explicit conversion                    compiled      length=22
```

The result looks reversed at first glance: the line that does not compile is **shorter** (15
characters), the line that does compile is **longer** (22 characters). Rejecting does not
shorten the source — it does the opposite. Rather than assume `a` will be widened, the compiler
wants that widening to be **visible** in the source, and it puts the cost of that on the party
writing it: a seven-character call.

**LB3.** The unit measured here is the character, not runtime or memory; the claim is not
"rejecting slows things down" but "rejecting does not shorten". A fifteen-character difference
seems small, but the same pattern repeats at every widening point in a source; the readability
gain is bought with a fixed writing cost per line.

The four do not all point the same direction. The fix for the unused import is a **deletion** —
the line disappears from the source entirely and the source shortens. The fix for the implicit
type conversion is an **addition** — a call wraps an existing line and the source grows. Both
rejections give the same class of result (not compiled), but the direction of the fix is
opposite. This breaks the impression that "rejecting" carries a single writing cost: some
rejection reasons push the source toward simplification, others push it toward writing intent
explicitly; the only thing they share is that neither leaves an **ambiguous range** in the
source.

The same holds for the fourth rejection (unused variable) in exactly the same way: deleting the
line `x := 1` shortens the source, but using `x` somewhere (writing `_ = x`) does not shorten
it either, it only adds a word. What stands in for rejection is never "write less"; it is
either delete or write your intent **explicitly**. Simplicity, counted among Go's design goals,
is not aimed at code length but at what appears in the source: a reader who sees the line
`int64(a)` knows the widening was deliberate; had they seen `var b int64 = a`, they could not
have known that.

This reading closes the tension opened earlier. Simplicity and writing intent explicitly
conflict at the level of the source's **length** but not at the level of its **rule count**:
the conversion rule the compiler recognizes is a single one — widening is always written — and
this single rule removes entirely from the reader the question "in which case is an implicit
conversion safe". The reader does not have to separately ask whether an assignment is implicit
or explicit, because the compiler already never lets the implicit one through. Simplicity here
sits not in a shrinking character count but in a shrinking number of **possible readings**.

The link to compile speed is built from the same point. None of the four rejections wait for
the rest of the source before deciding: an unused import is evident while that file alone is
being read; an implicit conversion is seen just by looking at the type on either side of the
assignment. Neither requires an inference dependent on what is happening elsewhere in the
program. Duration is never measured in this lesson, but the **locality** of the rejection
decisions — each looking only at its own expression — leaves an observable trace of where
compile speed comes from.

#### Summary

- The compiler is called from inside the program in this lesson and throughout the rest of the
  course; only whether it passes is read, the error text is never read.
- An unused variable, an unused import, an unwritten return value, and an implicit type
  conversion — all four are harmless at runtime yet stop compilation.
- The three reasons fall into three distinct classes: a name written but never read, a body
  that fails to keep its promise, a widening invisible in the source.
- Rejecting the implicit conversion does not shorten the source: the line that fails to compile
  is 15 characters, its compiling counterpart is 22 characters.
- Rejection is a decision that narrows what stands in for the unwritten — in these four cases,
  the language chooses to never allow it rather than put a defined value there.

#### Next Step

The four rejection reasons were about things a source writes **in excess** or leaves short of
a promise; none of them was about the smallest set of things a source **needs** in order to
run at all. The next lesson asks about that smallest set: which parts are mandatory for a
source to compile and run, and what does removing one of them cost when it is written not in
the source itself but in a module file?

### Environment Setup and the First Program

Source: https://academia.sh/en/courses/go-fundamentals/setup-and-first-program

Three things are mandatory for a Go source to compile: a package declaration, an entry point (func main), and a module file; when one of the three is missing, compilation is rejected even if the rest of the source has not changed at all — the module file never sits inside the source itself.

The previous lesson measured four rejection reasons; all of them arose when something
**extra** was written into the source or a promise went unkept. This lesson asks the reverse:
what is the smallest set of things that must be written for a source to compile and run at
all? A Go program asks for three things at once — a package declaration, an entry point, a
module file — and when one of them is missing, compilation stops no matter how correctly the
rest of the source is written.

The How Computers Work course (M01/K01) built, as a concept, how a program is started by the
operating system, the notion of an entry point, and how a process reaches its first
instruction. **That concept is not rebuilt here** — one sentence is enough to state the
difference: there, it was explained that an entry point **has to exist**; here we measure
**which signature** and **at what point in compilation** Go enforces that requirement.

#### Three Mandatory Parts

The three each sit in a different place. The package declaration (`package main`) is the
**first line** of the source and states which package it belongs to; for an executable
program, this package name has to be `main`. The entry point is the `func main()` function
defined inside the `main` package, and it is the first Go instruction called directly by the
operating system, not by a virtual machine. The module file, on the other hand, sits
**outside** the source — a separate text file living in the same directory as the source,
`go.mod`, and it is the only thing that tells the compiler "this directory is the root of a
module".

All three being mandatory is not a coincidence — it is the answer to three separate questions.
The package declaration answers "what is this source part of" and states that not even a
single source file can stand without a package — in Go there is no such thing as a
packageless source, even the smallest program has to carry a package name. The entry point
answers "does execution start here" — a package can hold more than one function, but there is
exactly one signature that tells the operating system which function to start from. The module
file answers "where does this package sit" and carries information independent of the package
itself — the same package name can repeat across many different directories, but which module
a directory belongs to is read only from the `go.mod` file in that directory (or one above it).

Since all three questions look "mandatory," it can be tempting to check them together as a
single checklist — one check asking "are all three complete." This lesson does the opposite:
it removes each one **one at a time** and reads the result separately, because the same
`not compiled` answer can hide three separate reasons, and knowing at which stage each of the
three arises is the first step to understanding why a source does not compile.

We are going to remove these three parts one at a time and measure the compile result. The
measurement tool is very close to the previous lesson's `compiles` function; the only
difference is that it also takes, as a separate parameter, whether the module file will be
written.

```go
// build.go — helper that compiles while checking each part
package main

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

func compilesWithParts(source string, writeMod bool) bool {
	dir, err := os.MkdirTemp("", "trial")
	if err != nil {
		return false
	}
	defer os.RemoveAll(dir)
	if writeMod {
		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 classify(ok bool) string {
	if ok {
		return "compiled"
	}
	return "not compiled"
}
```

**LB4.** The measurement tool opens one parameter further than the previous lesson:
`writeMod` decides whether the module file is written for that run. The source itself is
never affected by this parameter; keeping that in mind is necessary to correctly read what the
fourth line below measures.

This extension looks small but carries a design decision: in the previous lesson, where the
unwritten thing sat was always **inside** the source — a line either existed or it did not. In
this lesson, for the first time, where the unwritten thing sits moves **outside** the source,
and the measurement tool has to represent that with a separate argument. The next section shows
why this distinction matters.

We run four cases in order: with nothing missing, with no package declaration, with no entry
point, with no module file.

```go
// parts.go — each of the three mandatory parts is removed one at a time
package main

import "fmt"

const completeSource = `package main

func main() {}
`
const noPackageSource = `func main() {}
`
const noMainSource = `package main
`

func main() {
	fmt.Printf("%-30s %s\n", "missing part", "result")
	fmt.Printf("%-30s %s\n", "nothing missing", classify(compilesWithParts(completeSource, true)))
	fmt.Printf("%-30s %s\n", "no package declaration", classify(compilesWithParts(noPackageSource, true)))
	fmt.Printf("%-30s %s\n", "no entry point (main)", classify(compilesWithParts(noMainSource, true)))
	fmt.Printf("%-30s %s\n", "no module file", classify(compilesWithParts(completeSource, false)))
}
```

```
missing part                   result
nothing missing                compiled
no package declaration         not compiled
no entry point (main)          not compiled
no module file                 not compiled
```

The first line is the control group: with nothing missing, the source compiles. Each of the
next three lines removes exactly one part from the control group, and the result changes in the
same direction each time — all three stop compilation. But the three do not stop it for the
same **reason**. With no package declaration, the compiler has no idea at all which package the
source belongs to; this is the earliest branch to be cut, because a package name is expected on
the source's very first line. With no entry point, the source **compiles** as a valid package,
but no executable is produced — there is no error in the source, it is only missing an
executable end; `go build ./...` counts this too as not compiled, because an entry point is a
mandatory contract for the `main` package. The fourth line is the most indirect and leads into
this lesson's limiting measurement.

Looking at the `noPackageSource` constant, exactly one line is missing: `package main`. When
that line is removed, what remains — `func main() {}` — is not meaningless on its own: it is a
function definition and its syntax is correct. The compiler's rejection here is not about
syntax but about **context**: every Go file is read as part of a package, and without a
package name it cannot place a file into any package at all. This is a different tone from the
four rejections in the previous lesson: those four sources violated syntax rules, whereas this
source is a syntactically complete function body that is **nameless**.

The first two rejections belong to a family close to the four rejections the previous lesson
measured: both are something left missing in the source's own syntax, and the compiler sees
this while **reading** the source. The absence of the entry point is caught at a different
stage. `go build` does two things one after the other: it first compiles the source into a
package, then it **links** an executable from that package. Package compilation accepts
`main.go` as a valid Go file; the linking stage looks for the `main` package to carry a
`func main()` function and stops when it cannot find one. This distinction matters because the
same source could compile without any problem in a different context — for instance as a
library package imported by another program; the entry-point requirement applies only to the
`main` package, only when an executable is being requested.

#### Two Files, One Package

Do the three mandatory parts have to sit in a single file? No. Two files in the same directory,
as long as they carry the same package declaration, count as belonging to **one package** and
are compiled together — even if one of them does not carry a `func main`. The `build.go` file
above and every other file used in this lesson were already showing this; here we build the
same pattern in its plainest form.

The practical importance of this shows up as a program grows. If everything had to be written
into a single file carrying `func main()`, a program's length would be limited by what a
single file can stay readable at. Because Go defines a package by its declaration and not by
file count, a package's responsibilities can be spread across more than one file — and this
split never changes the compile result: `build.go`, `helper.go`, and `together.go` would
compile just as well as three separate files or merged into one, and would produce the same
binary. The decision to split exists purely for the convenience of the reader and the writer.

```go
// helper.go — second file, belongs to the same package
package main

import "fmt"

func greet(name string) string {
	return fmt.Sprintf("hello, %s", name)
}
```

```go
// together.go — two separate files, one package, compiled together
package main

import "fmt"

func main() {
	fmt.Println(greet("package"))
}
```

```
hello, package
```

**LB5.** `together.go` calls the `greet` function defined in `helper.go` without writing any
import at all. What is measured in this lesson is not the mere existence of a second file — we
were already using one before reaching this lesson — it is that two separate files compile into
**a single binary**: `go run .` reads every `.go` file in the directory, never looks for an
import between them, and checks only that the package name matches.

This behavior asks us to re-read the "nothing missing" line from the earlier measurement too.
The single-file `completeSource` used in that line was written and compiled on its own,
**outside** `build.go` — `classify` and `compilesWithParts` were not being called there, the
source only carried `package main` and an empty `func main`. Here, on the other hand,
`together.go` depends on `helper.go` in the same directory, and that dependency needs no import
crossing a file boundary at all. The two measurements complement each other: one shows the
smallest possible shape of a package, the other shows the shape of a package spread across more
than one file.

This also explains why a package declaration is written per file. The compiler learns which
files together form a whole not from the **file name** but from each file's own first line.
`build.go`, `helper.go`, and `together.go` all write `main`, and all three join the same
package; if any one of them had written a different name, compilation would stop right there,
even inside the same directory.

One consequence of this behavior is that imports work at **two separate levels**.
`together.go` imports the `fmt` package on its own, because it uses a piece of the standard
library; but it does not import `greet`, defined in `helper.go`, because that already belongs
to the same package. The import line is only needed for names that cross a **package
boundary** — inside the same package, a file boundary carries no meaning. The course's Packages
topic will flip this boundary the other way: there, files will still stay in the same package,
but some names will not be visible **outside** the package at all.

#### Limiting Measurement: The Module File Is Not Part of the Source

Return to the fourth line. The source used in the `no module file` line is **byte for
byte identical** to the one used in the first line — the `completeSource` constant passes
through both calls unchanged. The only difference is that the `writeMod` argument given to
`compilesWithParts` is `false`: no `go.mod` is ever written into the temporary directory.

**LB6.** What is measured in this line is not a source error but a **missing context**. The
source's own package declaration and entry point stand complete; the compiler still rejects it,
because it will not agree to compile a directory without knowing which module it belongs to.

This line separates qualitatively from the lesson's first two rejections. Removing the package
declaration or the entry point meant **touching** the source; here the source was never even
opened, only another file in the directory was deleted. A reader looking at the
`completeSource` constant could not tell from the source itself why it sometimes compiles and
sometimes does not — the answer sits outside the source, in the directory's contents.

The practical consequence is this: copying a source file into another directory is useless if
that directory has no module file — the source will not compile even if it was copied exactly
as is. The module file is not **part of** the source the way the package declaration is; it is
the source's **context**. This distinction matters because, even though both are counted
"mandatory," they are mandatory for different things: the package declaration carries the
source's own identity, the module file states **where** the source sits. The course's Packages
topic will deepen this distinction; what is measured here is only the requirement itself — a
source, however correctly written, never turns into a binary without a module file.

The measurement tool used in this lesson never changed the module file's **content** — it only
changed whether it existed. A module file, even in its smallest form asking for no outside
dependency at all, is mandatory on its own; having no dependency line added to it is not a
reason for the compiler to reject the module. What is mandatory is the module file's
**existing**, not how full its content is.

The module file being independent of the source also means the same source can be moved
**under more than one** module. Changing directory and writing a fresh `go.mod`, without
changing the source at all, makes it part of another module. Changing the package declaration,
on the other hand, means changing the source itself. This difference between the two shows that
the word "mandatory" does not carry a single meaning — something can be mandatory **inside**
the source, or mandatory **around** the source, and the cost of removing each is not the same:
one breaks the source, the other only invalidates the place where the source sits.

This threefold distinction — identity, contract, context — also gives a retrospective frame to
the four rejections measured in the first lesson. The unused variable and the unused import
were about the source's own identity: every name written in the source should deserve to stay
part of the source. The unwritten return value was a broken contract: the signature makes a
promise, the body must keep it. The third category added in this lesson — context — never
appeared in the first lesson, because the four sources there were already being written into a
module. Seeing that mandatoriness can sit in three separate places will keep proving useful
later in the course: a variable's zero value will belong to its **identity**, a function's
named return will belong to its **contract**, and a package's export rule will belong to its
**context**. None of these three categories will be called by these names anywhere in this
course — what is built here is only a reading habit: the first question on seeing a
`not compiled` answer should not be "which line is missing" but "where does the missing thing
sit".

#### Summary

- Three things are mandatory for a Go source to compile: a package declaration, an entry point
  (`func main`), a module file.
- Compilation stops when any of the three is missing, but not for the same reason: one leaves
  the source's identity incomplete, one its contract, one its context.
- Two files in the same directory see each other and compile into a single binary without
  writing an import, as long as they carry the same package name.
- The module file is the source's context, not its part: the same source, unchanged, stops
  compiling once the module file is removed.
- Mandatoriness does not carry one meaning — what is mandatory inside the source and what is
  mandatory around the source cost different things when removed.

#### Next Step

This lesson measured what is required for a source to **run**; nowhere did it ask what is or
is not written into a value. `func main() {}` was an empty body and had no variable inside it
at all. The next lesson fills that gap: do you have to write a variable's type when defining
it, where does short declaration infer the type from, and who fills the unwritten values in a
sequential constant list?

### Variables and Constants

Source: https://academia.sh/en/courses/go-fundamentals/variables-and-constants

Short declaration decides a variable's type without ever writing it into the source; only two of a six-constant iota sequence have a value written in the source — the other four come from repeating the previous expression — and the same short-declaration line is rejected at package level while it is accepted inside a function.

The previous lesson measured which parts are mandatory for a source to run, but throughout
`func main() {}` the body stayed empty — no question was ever asked about what was or was not
written into a value. This lesson steps into that gap. Do you have to write a variable's type
when defining it, where does short declaration infer the type it does not write, and who fills
in the in-between values of a sequential constant list?

The Programming Foundations course (M01/K02) built variable definition and binding using
Python; there, binding a name to a value happened with a single piece of syntax and the type
never appeared in the source at all. **That concept is not rebuilt here** — one sentence is
enough to state the difference: in Go a name **has a type**, and that type is either written or
inferred from a value; no variable is ever left typeless. What this lesson asks is where that
inference comes from.

#### Where Short Declaration Infers the Type From

A variable is defined in Go in two forms: `var name type = value`, or short declaration,
`name := value`. In the second, the type is never written at all; the compiler determines the
variable's type by looking at the **type of the assigned value itself**. This is one of the
examples of "an unwritten thing having a defined counterpart" — the type was not written, but
a variable having no type is not an available option; the compiler **infers** that type from
the value.

We can ask what type the inference lands on at runtime using the `%T` format verb; this verb
prints the name of a value's **static** type, and that name is fixed at compile time — it gives
the same answer on every run.

```go
// types.go — type inferred by short declaration
package main

import "fmt"

func main() {
	integer := 42
	floatingPoint := 3.14
	str := "text"
	boolean := true

	fmt.Printf("%-14s %-8s value=%v\n", "integer", fmt.Sprintf("%T", integer), integer)
	fmt.Printf("%-14s %-8s value=%v\n", "floatingPoint", fmt.Sprintf("%T", floatingPoint), floatingPoint)
	fmt.Printf("%-14s %-8s value=%v\n", "str", fmt.Sprintf("%T", str), str)
	fmt.Printf("%-14s %-8s value=%v\n", "boolean", fmt.Sprintf("%T", boolean), boolean)

	var wideType float64 = 42
	fmt.Printf("%-14s %-8s value=%v\n", "wideType", fmt.Sprintf("%T", wideType), wideType)
}
```

```
integer        int      value=42
floatingPoint  float64  value=3.14
str            string   value=text
boolean        bool     value=true
wideType       float64  value=42
```

**LB7.** `%T` is a static name read from the source itself; it carries no machine-, version-,
or run-dependent information at all. This verb is what will be used throughout this lesson and
the rest of the course to read a type's name, because it never violates the ban on
environment-dependent data.

This shows a distinction that matters for the measurement: what `%T` prints is not a memory
address or the variable itself, it is the name of the **type information** the compiler
assigns to that variable. Running the same source in two different runs, `%T` always produces
the same string, just like `compiles` in the previous lesson always produced the same
`compiled`/`not compiled` answer. Every measurement tool in this course shares these two
properties: being deterministic and reading nothing that depends on the environment.

The first four lines show short declaration landing on four separate types from four separate
values: writing an integer gives `int`, writing a decimal gives `float64`, writing something in
quotes gives `string`, writing `true`/`false` gives `bool`. In none of them does the type name
appear in the source; the `:=` sign on the left of short declaration is shorthand for "infer
this name's type from the value on the right."

This inference works by looking at the assigned value's **written form**, not at some runtime
test. When `42` is written, the compiler sees while reading the source that this is an integer
constant; this decision is made without producing any value, without ever running the program.
This shows that short declaration is also a compile-time decision — only the type's name is
not written in the source, the decision itself is still made during compilation, before
runtime. Short declaration leaves nothing at all to runtime; what is unwritten is only the
**visible sequence of letters**, not the decision.

#### How a Type Written With `var` Diverges

The fifth line takes a different path. If `42` had been written with short declaration it
would have been `int` — the first line already showed that. Here the same `42` is made
**explicitly** `float64` by the line `var wideType float64 = 42`. Where short declaration
infers the type from the value, `var` opens a path to setting the type by hand; the difference
between the two is **who decides**. In short declaration, the value decides; in `var`, the
writer decides.

This should not be confused with the implicit conversion rejection measured in the previous
lesson. `var wideType float64 = 42` is not a conversion, it is an **initial value assignment**
— `42` here is an untyped constant, and untyped constants take shape based on the target type.
A conversion comes into play when a **variable** whose type is already fixed is assigned to
another variable (`var a int32 = 1; var b int64 = a`). That difference will be measured again
later in the course; what needs to be noted here is that suppressing short declaration's result
with `var` is not a rejection, it is a **preference**.

`var` syntax has one more advantage: it can be used without an initial value. Writing
`var count int` defines `count` without giving it any value at all, and the compiler does not
reject this line — unlike short declaration, `var` does not demand a value. In this case the
value the variable takes is the **zero value**, the topic of a later lesson in this course;
here it is only worth noting that `var` allowing a type to be written and `var` working without
a value written come from the same root — both, unlike short declaration, do not depend on the
requirement of "inference from a value."

#### Iota: Who Fills In the Unwritten Constant Values

Constants are defined with `const`, and when a group of them is listed one after another, a
special counter named `iota` can be used. `iota` starts at zero and increases by one on every
line of a `const` block; if a line has no expression of its own, the line repeats the previous
line's expression **as is**, and only the value of `iota` is updated.

This solves a real problem: numbering a set of states by hand (`Pending = 0`, `Running = 1`,
`Completed = 2`, ...) is both repetitive writing and requires every number to be shifted by
hand whenever a new state is inserted in between. `iota` ties this numbering to the line's
**order**; when a line is inserted in between, every value after it shifts automatically, none
of them is updated by hand. This is a mechanism built for state sets where the numbers
themselves do not matter, only their **order and uniqueness** matter.

```go
// constants.go — six constants with iota, how many values are written in the source
package main

import "fmt"

const (
	Pending  = iota
	Running
	Completed
	Failed  = iota * 100
	Cancelled
	TimedOut
)

func main() {
	fmt.Printf("%-12s %d\n", "Pending", Pending)
	fmt.Printf("%-12s %d\n", "Running", Running)
	fmt.Printf("%-12s %d\n", "Completed", Completed)
	fmt.Printf("%-12s %d\n", "Failed", Failed)
	fmt.Printf("%-12s %d\n", "Cancelled", Cancelled)
	fmt.Printf("%-12s %d\n", "TimedOut", TimedOut)
}
```

```
Pending      0
Running      1
Completed    2
Failed       300
Cancelled    400
TimedOut     500
```

**LB8.** Only **two** of the six constants (`Pending`, `Failed`) have an expression written in
the source; in four of them (`Running`, `Completed`, `Cancelled`, `TimedOut`) only a name sits
on the line, no value is ever written. The value of these four comes from two sources: the
`iota` value at that line's own position, and the expression **repeated** from the previous
line.

The table confirms this. `Running` and `Completed` repeat `Pending`'s expression (`iota`), but
are computed with the `iota` value of their own line (1 and 2 respectively) — that is why they
climb `0, 1, 2`. `Cancelled` and `TimedOut` follow the same logic, repeating `Failed`'s
expression (`iota * 100`), but multiplied by their own `iota` values (4 and 5) — climbing
`300, 400, 500`. `iota` is a **single counter** across the whole block; it does not reset when
a second expression appears in the group, it keeps increasing without a break, following line
order.

This gives the course's most indirect answer yet to the question "what stands in for the
unwritten." Nothing being written on a line does not mean that line gets a **random** or
**empty** value; two rules together — the next `iota` value and the repetition of the previous
expression — produce a fully defined result. All four unwritten values fall, for this reason,
into the `defined` class, not `open`: the result does not change from run to run, and a reader
of the source can reach the same numbers on paper too.

Since every constant here is used in a `fmt.Printf` call, we do not run into the unused
variable rejection; the rule measured in the first lesson still holds here — an unused
rejection never applies when a constant is defined at package level, because Go applies that
rejection only to **local** variables. A constant or variable defined at package level does not
stop compilation even if it is never read. This distinction will come up again in the next
section.

This single rule narrows the first lesson's rejection without contradicting it at all. The
"unused variable" rejection measured in the first lesson was the most commonly encountered of
the four rejections, and the question of which context it applies in was never asked there.
Here we see that the rejection is not universal: leaving a local variable unused is rejected,
leaving a package-level constant unused is not. There is a reason for this — a package-level
constant can be used somewhere entirely different, even in another file of the same package or
one exported to a different package altogether; the compiler, reading a single file, cannot
know whether that use will happen somewhere else later. A local variable, on the other hand, is
bound to its own function body, and the compiler can see in the same glance whether it is used
or not.

`iota` also has a way of being ignored: if the blank identifier `_` is written instead of a
name on a line, `iota` still increases, but no name is produced for that position. This is
another use of the blank identifier measured in the first lesson — there it was a way to pass
over an unused value **without naming it**; here it is a way to **skip** a number in the
sequence. In both cases the same word carries the same meaning: "I know this, but I will not
bind it to a name."

#### Limiting Measurement: Short Declaration Does Not Work Everywhere

Short declaration is not as universal as it appears. To measure this, we return to the first
lesson's compiler call: we will try the same line in two different contexts and classify the
result.

```go
// compiler.go — helper that calls the compiler from inside the program
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
}

func classify(ok bool) string {
	if ok {
		return "compiled"
	}
	return "not compiled"
}
```

Try the same `x := 5` line in two different contexts: once outside a function, at
package level; once inside a function.

```go
// limiter.go — is short declaration rejected at package level
package main

import "fmt"

func main() {
	packageLevel := `package trial
x := 5
func F() { _ = x }`
	functionLevel := `package trial
func F() { x := 5; _ = x }`

	fmt.Printf("%-24s %s\n", "at package level", classify(compiles(packageLevel)))
	fmt.Printf("%-24s %s\n", "inside a function", classify(compiles(functionLevel)))
}
```

```
at package level         not compiled
inside a function        compiled
```

**LB9.** Both sources carry the same `x := 5` line; the only difference is whether that line
sits inside or outside a function body. The compiler only recognizes short declaration as a
valid **statement** inside a function body; at package level it expects a declaration header
like `var` or `const`, not `:=`.

This boundary shows why short declaration is not a "shortcut" but a **context-specific**
piece of syntax. Short declaration saves you from writing the word `var` and a type name over
and over while defining many variables one after another inside a function; at package level
there is no need for this shortcut, because package-level declarations are already mostly
written one at a time, with `var` or `const`, and their initialization order is resolved not
by file order but by dependency order — that resolution will come up again in the next lesson,
over basic types.

This rejection belongs to a different class than the four rejections in the first lesson.
There, the four rejected sources were syntactically valid; the compiler merely did not
**want** a particular pattern. Here `x := 5` is syntactically **invalid** at package level —
the language's grammar only recognizes the `:=` sign in a statement context, and package level
is not a statement context, it is a declaration list. This shows that the "rejected" class
itself has two subtypes: a pattern can be grammatically valid but the compiler **does not
want** it (the first lesson's four rejections), or the pattern itself is **never defined** in
that context (this lesson's rejection). Both produce the same `not compiled` answer, but one
is a rule violation, the other a grammar mismatch.

The practical consequence of this distinction is that the writing form has to **change** when
a value is meant to be defined at package level — moving from short declaration to `var` is not
a preference, it is a requirement. A line moved from inside a function to package level does
not just change its location; its writing form has to change too.

This lesson's three measurements — short declaration's type inference, `iota`'s filling of
constants, short declaration's rejection at package level — are three faces of the same
question. In all three, something is unwritten: a type name, four constant values, the
validity of a declaration form. In all three, the language does not leave the gap random: in
one it fills it by looking at a value, in one it fills it with a counter and a repetition rule,
in one it finds nothing to fill it with and rejects. The course's first two lessons measured
"what gets rejected" and "what is mandatory"; this lesson measures an in-between region that is
neither rejected nor mandatory — **inference**.

#### Summary

- Short declaration (`:=`) infers a variable's type from the assigned value's own type,
  without ever writing it in the source.
- The `var name type = value` form lets the writer set the result of inference **by hand**;
  this is not a conversion, it is an initial value assignment.
- In a six-constant `iota` sequence, only two have a value written in the source; the other
  four are produced from the line's own `iota` value combined with a repeated previous
  expression.
- `iota` is a single counter across an entire `const` block and does not reset when a second
  expression appears.
- The same short-declaration line compiles inside a function body but is rejected at package
  level — short declaration is a valid statement only inside a function.

#### Next Step

In this lesson a variable's type was always inferred from a **value** — `int` from `42`,
`float64` from `3.14`. But those types themselves were never questioned: what happens when an
integer overflows, what should be expected when two floating-point values are compared, and
what is the `rune` type actually a stand-in for? The next lesson takes up basic types by
measuring what each of them does in an unwritten case.

### Basic Types

Source: https://academia.sh/en/courses/go-fundamentals/basic-types

Four unwritten cases are run through the same frame: an untyped constant's default type turns out to be int, a sum that overflows an int8 wraps to -128, two float64 values added at runtime do not come out equal to 0.3, and a character literal's type is actually int32 — rune is only an alias. Limiting measurement: the implicit-conversion rejection does not hold for constants.

The previous lesson always inferred a variable's type from a value, but the types themselves
were never questioned. This lesson asks four questions at once: what is the default type of a
number written with no context, what happens when an integer overflows its bound, does adding
and comparing two floating-point values give the result we expect, and what actually is the
`rune` type we keep naming? All four are read with the same three classes — defined, rejected,
open — but these four are not part of the course's central fifteen-item frame; they are this
lesson's own observation.

The Programming Foundations course (M01/K02) built basic data types, operator precedence, and
explicit versus implicit conversion using Python. **Those concepts are not rebuilt here** — one
sentence is enough to state the difference: there, types were flexible at runtime and when a
value overflowed the result widened into a bigger integer; in Go every integer type has a
**fixed width**, and overflow is met not with widening but with wraparound. This lesson
measures what happens at the edge of that width.

#### Four Unwritten Cases, One Frame

```go
// basics.go — four unwritten cases: default type, overflow, floating-point comparison, rune
package main

import "fmt"

const Number = 5

func main() {
	fmt.Printf("%-28s type=%-8s value=%v\n", "untyped constant default", fmt.Sprintf("%T", Number), Number)

	var b int8 = 127
	b++
	fmt.Printf("%-28s type=%-8s value=%v\n", "integer overflow", fmt.Sprintf("%T", b), b)

	var x, y float64 = 0.1, 0.2
	sum := x + y
	fmt.Printf("%-28s equal=%-6v sum=%v\n", "floating-point comparison", sum == 0.3, sum)

	c := 'A'
	fmt.Printf("%-28s type=%-8s value=%v\n", "character literal", fmt.Sprintf("%T", c), c)
}
```

```
untyped constant default     type=int      value=5
integer overflow             type=int8     value=-128
floating-point comparison    equal=false  sum=0.30000000000000004
character literal            type=int32    value=65
```

**LB10.** All four lines are read with the same three classes (defined, rejected, open), but
they are not part of the fifteen-item core; they are a new observation added to the shared
frame. The 8/4/3 split in the Zero Values lesson does not include these four lines.

Three of the four lines are **defined**, one is **open**. None of them is **rejected** — all
four are valid Go source, all four compile and run; what differs among them is whether the
result comes from the source, from a fixed rule, or from the representation at runtime. Let us
read them in order.

#### The Default Type of an Untyped Constant

The `Number` constant is defined with the value `5`, and no type is ever written —
`const Number = 5`, not `const Number int = 5`. A constant like this is **untyped** in Go: on
its own it is neither an `int` nor a `float64` nor any other type, it is only a numeric value,
and it takes shape based on where it is used. When `Number` passes as an `interface{}`
argument to `fmt.Printf`, a concrete type is needed to format it, and the compiler brings in
the untyped constant's **default type** at that point: for integer constants that default is
`int`.

This is a different mechanism from the short declaration measured in the previous lesson.
There, the type was inferred from the assigned value's **own** type; here the value has no
type at all to begin with, and a type appears only when a context forces it to. If we had
assigned `Number` to a `float64` variable, the same constant would have become `float64`
without writing any conversion at all — because an untyped constant fits **by itself** into
whatever type the target allows. This distinction will reappear in the lesson's limiting
measurement.

The default type is not limited to `int`; every kind of constant has its own default. A
constant in quotes takes `string` as its default, `true`/`false` takes `bool` as its default, a
constant written with a decimal point takes `float64` as its default. None of these are ever
written in the source — the previous lesson showed short declaration inferring a type from a
**value**; here what we see is that the value itself, unless some context forces it, stays
**untyped** on its own. The type only appears once it is placed somewhere.

#### Integer Overflow: A Defined Wraparound

The `int8` type is 8 bits wide and, being signed, holds values between -128 and 127. When we
give `b` the value 127 and increment it by one, the result falls outside that range — but the
program does not crash, and it produces no error either. The result becomes **-128**: the
wraparound turns from the top end of the range back to the bottom.

**LB11.** The overflow here is produced deliberately. Go does not count integer overflow as an
error; it wraps around in a **defined** way, using two's complement arithmetic. This adds a
third answer to the question of "what stands in for the unwritten": no check was written to
prevent the overflow, but the language does not put a random value into that gap, it puts an
arithmetically predictable one.

The wraparound rule is simple: a value that exceeds `int8`'s upper bound (127) by one continues
from the lower bound (-128). This is a natural consequence of two's complement representation —
the same bit pattern corresponds to a negative number when read as signed and a positive number
when read as unsigned, and addition stays the same at the bit level, only its interpretation
changes. The compiler adding no check here is not an oversight — the wraparound behavior merely
makes visible, as is, an operation the hardware was already doing.

The wraparound being predictable does not make it **safe**. The result `-128` is not some
approximate version of `b`'s real mathematical value (128) — it is an entirely different
number, and its sign has flipped too. If a computation crosses this boundary unnoticed, nowhere
is the result flagged as **wrong**; the overflow does not belong to the family of rejections
measured in the first lesson, it belongs to the `defined` class the measurement core names.
The language puts something there, but what it puts is not always the arithmetically expected
thing.

This behavior shows why an integer type's width needs to be chosen **explicitly** in the
source. Had a wider type (`int32`, `int64`) been chosen instead of `int8`, the same addition
would never have produced any wraparound at all; the wraparound comes not from the value
itself but from the width chosen. This also shows that choosing an integer type is a decision
that sits **outside** short declaration, measured in the previous lesson — short declaration
never lands on `int8` unless `b := int8(127)` is written, it always falls back to `int` by
default. A narrow type like `int8` can only be chosen with `var` or an explicit conversion, and
that choice also brings its overflow bound along with it.

#### Floating-Point Comparison: A Result Left Open

The third line initializes two `float64` variables, `x` and `y`, with `0.1` and `0.2`, adds
them, and checks whether the sum equals `0.3`. The result is `false`. The sum itself is not
`0.3` either — it is `0.30000000000000004`.

**LB12.** The decimal digits printed here are not environment-dependent data — this is the
**deterministic** result of IEEE 754 binary fractional representation, and it reaches the same
digits on every run. `0.1` and `0.2` cannot be written as a finite fraction in binary, the same
way `1/3` cannot be written as a finite fraction in base ten; both round to the nearest
`float64` value, and their sum is not the true mathematical sum but the sum of those two
rounded values. This rounding is not specific to Go — the `float64` representation is not the
language's own specification, it is the IEEE 754 standard itself, and the language inherits
that representation as is.

This line falls into the **open** class, because the language puts nothing here and the
compiler rejects nothing either. `==` is a valid comparison, it compiles and runs; only whether
the result is "what one would mathematically expect" depends on the representation at runtime.
The code says what it does — it compares two `float64` values for equality — but it does not
say in advance whether that will match the reader's mental expectation that "0.1 plus 0.2 makes
0.3."

There is an interesting detail here: the untyped-constant mechanism we saw with `Number` could
also have affected this line. If `0.1 + 0.2 == 0.3` had been written with all three sides as
untyped constants, the compiler would have computed that sum **at compile time**, with
arbitrary precision, and the result really would have come out `true` — because constant
arithmetic relies on exact fractional computation, not binary rounding. The behavior left open
is a property not of constants but of **runtime variables**: because `x` and `y` are variables,
their addition happens at runtime, with binary values that were already rounded beforehand.

#### A Character Being an Integer

The fourth line writes `c := 'A'`. A character in single quotes is, in Go, a **rune literal**,
and when its type is inferred by short declaration it comes out `int32` — no separate `rune`
type appears, because `rune`, in the language's own specification, is nothing more than an
**alias** for `int32`. `%T` prints both under the same name, `int32`.

The value itself confirms this too: the output shows `value=65`, not `A`. The `%v` format verb
of `fmt.Printf` prints a value according to its own type, and `int32`'s default printing is a
decimal number — to see the character we would need to write `%c`. This means the claim "a rune
is an integer" shows up not only in the type system but also in formatting: the language does
not keep a `rune` in some special "character" box, it treats it like an ordinary number, and
its appearing as a readable character is entirely the choice of the formatting call.

This fourth line comes from the same family as the first line: both measure a literal's
**default type**. The difference is that in the first, the default type is `int`, while in the
character literal, the default type is directly `rune` itself (`int32`). When `'A'` is
written, the language knows this is a code point, and code points are represented as integers;
the difference between writing `65` and writing `'A'` is, for this reason, purely one of
**readability** — both land on the same type, the same value.

This also explains why the word `rune` is never translated in this course: `rune` is not one of
Go's own keywords, it is a **type alias** the standard specification defines — the same way
`byte` is an alias for `uint8`. An alias, once written in the source, is resolved by the
compiler to its real name **immediately**; no separate `rune` type tag is carried at runtime,
and `%T` confirms this. Throughout this lesson we avoid using the word "character" for anything
measured, because what we are measuring is exactly that a character is actually an integer —
the course's Composite Types topic will deepen this distinction in its Strings and Runes
lesson, together with why a string is a byte sequence and not a character sequence.

#### Limiting Measurement: The Implicit-Conversion Rejection Does Not Apply to Constants

One of the four rejections measured in the first lesson was the rejection of implicit type
conversion: an `int32` variable could not be assigned to an `int64` variable without a written
conversion. This rule does not hold for **untyped constants**. To measure this, we return to
the first lesson's compiler call.

```go
// compiler.go — helper that calls the compiler from inside the program
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
}

func classify(ok bool) string {
	if ok {
		return "compiled"
	}
	return "not compiled"
}
```

```go
// limiter.go — the implicit conversion rejection does not apply to constants
package main

import "fmt"

func main() {
	constantFits := `package trial
func F() { var target int64 = 5; _ = target }`
	variableMismatch := `package trial
func F() { var source int32 = 5; var target int64 = source; _ = target }`

	fmt.Printf("%-32s %s\n", "untyped constant fits target", classify(compiles(constantFits)))
	fmt.Printf("%-32s %s\n", "variable carrying same value", classify(compiles(variableMismatch)))
}
```

```
untyped constant fits target     compiled
variable carrying same value     not compiled
```

Both lines carry the same numeric value (`5`) and assign it to the same target type
(`int64`). In the first line the source is an **untyped constant** — the literal `5` is not
bound to any type, it fits directly into `int64`, and the compiler counts this as an initial
value assignment, not a conversion. In the second line the same `5` value is first assigned to
an `int32` variable; from that point on `5` is no longer untyped, it is a value **materialized
as `int32`**, and its move to `int64` runs into the implicit-conversion rejection measured in
the first lesson.

This shows the difference between "same value" and "same type." The compiler's rejection
decision looks not at the numeric value being carried but at the source's **type**. An untyped
constant carries no width at all until it is materialized; a variable, on the other hand, locks
into a specific width the moment it is defined. The rule measured in the first lesson needs to
be rewritten for this reason: an implicit conversion is rejected between two **typed** values;
an untyped constant fitting its target is not a conversion, it is a consequence of the
constant's own nature.

This boundary can also be tested with a "what if there were a fourth" question: what would
happen if we wrote `500` instead of `5` and made the target `int8`? In that case the untyped
constant would not **fit** the target, and compilation would still be rejected — but this would
not be a conversion rejection, it would be an **overflow rejection**: since the compiler knows
a constant's value at compile time, it sees in advance that it will not fit the target and
never compiles the source at all. This is a completely different mechanism from the runtime
overflow measured in the second section (`int8` exceeding 127): there the overflow **wrapped**
while running; here the overflow is **rejected** while compiling, because the constant's value
is already written in the source and the compiler does not need to run the program to compute
it.

All four measurements show the same distinction from different angles: as long as a constant's
type is not written in the source it stays **suspended** and only becomes fixed once some
context materializes it; a variable's type, on the other hand, becomes fixed the moment it is
defined and, from that point on, travels together with its width. The default type in the
first line, the character being an integer in the fourth line, and the constant-versus-variable
distinction in the limiting measurement — all three are different views of this same suspended
state. Only the second and third lines stand apart from this family — they are not about being
left suspended, they are about the bounds of an already-materialized type (`int8`'s width,
`float64`'s binary representation).

#### Summary

- An untyped integer constant's default type, when used with no context, is `int`.
- A sum that exceeds `int8`'s bound wraps to -128 without producing an error — the overflow is
  defined, but not the one arithmetically expected.
- Two `float64` values added at runtime can compare `false` against a constant that looks
  mathematically equal; this result is left open.
- `rune` is an alias for `int32`; a character literal's default type is directly this.
- The implicit-type-conversion rejection applies only between typed values — an untyped
  constant fits its target type on its own.

#### Next Step

Three of the four unwritten cases measured in this lesson found a defined counterpart, one was
left open. But none of them asked the case where a variable **never** receives a value at
all — if we wrote `var b int8` and never gave it a value, what would `b` become? The next
lesson carries that question to the center of the course: it gathers fifteen separate unwritten
cases into a single frame and measures that eight are defined, four are rejected, three are
left open.

### Zero Values

Source: https://academia.sh/en/courses/go-fundamentals/zero-values

Fifteen unwritten cases are run through a single frame: eight find a defined counterpart from the language, four are rejected by the compiler, three are left open to runtime. Limiting measurement: nil can be appended to a slice but cannot be written to a nil map — both are the same kind of zero value.

The previous lesson ended without asking what would happen if we wrote `var b int8` and never
gave it a value. That question is the real one this course has been deferring since its
opening: when a variable is left without an initial value, what does the language find there?
This lesson asks that question not in one example but in fifteen separate places, and sorts
each into one of three classes — **defined**, the language puts something in place of the
unwritten; **rejected**, the compiler will not accept the unwritten state at all; **open**, the
result is left to runtime. The numbers referred to throughout the rest of the course are
produced here.

The Programming Foundations course (M01/K02) built binding a variable to a name using Python;
there, a name could not exist without being given a value. **That concept is not rebuilt here**
— one sentence is enough to state the difference: in Go a variable can exist without ever being
given a value, because the type system has already assigned every type a **zero value** in
advance. What this lesson measures is what that zero value is for each type, and whether it is
really "there" in every situation.

#### The Premise

The single most useful question when reading a Go program is not what is written but what is
not. Every variable left without an initial value has a defined value; an unused variable stops
compilation; a nonexistent key returns two values at once; a variadic parameter given no
argument at all becomes an empty slice too. In the same language, some unwritten things have no
counterpart at all, and the program stops while running.

The measurement reduces this triple to a single question: for each unwritten case, does the
language put something in place, does the compiler reject it, or is the result left open? The
second half of finding the answer calls the compiler **from inside the program**: a piece of
source is written into a temporary package, the compiler is run, and only whether it passed is
read — the error text is never read, because what is being measured is not "why was it
rejected" but "was it rejected."

#### The Core: An Observer That Sorts Into Three Classes

```go
// unwritten.go — what does the unwritten thing become: does the language put something defined, does the compiler reject it, or is it left open
package main

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

var defined, rejected, open int

func observe(name, class, evidence string) {
	switch class {
	case "defined":
		defined++
	case "rejected":
		rejected++
	default:
		open++
	}
	fmt.Printf("%-42s %-11s %s\n", name, class, evidence)
}

func summarize() {
	fmt.Printf("unwritten: %d defined, %d rejected, %d open\n", defined, rejected, open)
}

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 panics(f func()) (p bool) {
	defer func() {
		if recover() != nil {
			p = true
		}
	}()
	f()
	return false
}
```

**LB13.** `observe` is the single entry point that writes every observation to a class; the
three package-level variables holding the counters (`defined`, `rejected`, `open`) only
increase here, so the totals stay consistent with the observations whenever `summarize` is
called.

**LB14.** `panics` runs a function and catches with `recover` whether it panicked, returning
only `panic=true`/`panic=false` — no stack trace is ever printed. This is the single method
used everywhere in the course that measures a panic.

These two helpers give a tool to each of the three classes this lesson measures: `observe` and
`summarize` hold the classification and the total, `panics` catches the open-ended behavior,
`compiles` (defined in the previous lesson's section) measures the rejection. Together, the
three reduce the body of a fifteen-line measurement to a single call shape: each line gives
`observe` a name, a class, and evidence, and everything else is handled by these three helpers.

#### Fifteen Unwritten Cases

```go
// measurement.go — fifteen unwritten cases, split into three classes
package main

import "fmt"

type Record struct {
	Name   string
	Number int
	Active bool
}

func namedReturn() (result int, err error) { return }

func variadic(numbers ...int) []int { return numbers }

func main() {
	fmt.Println("-- what stands in for the unwritten --")

	var integer int
	observe("an integer with no initial value", "defined", fmt.Sprint(integer))

	var slice []int
	observe("a slice with no initial value", "defined",
		fmt.Sprint("nil=", slice == nil, " length=", len(slice), " appendable=", append(slice, 1)))

	var m map[string]int
	observe("reading a map with no initial value", "defined", fmt.Sprint(m["missing"]))

	writePanicked := panics(func() { m["new"] = 1 })
	observe("writing to a map with no initial value", "open", fmt.Sprint("panic=", writePanicked))

	var pointer *Record
	readPanicked := panics(func() { _ = pointer.Number })
	observe("reading a pointer with no initial value", "open", fmt.Sprint("panic=", readPanicked))

	var record Record
	observe("unwritten struct fields", "defined", fmt.Sprintf("%q %d %t", record.Name, record.Number, record.Active))

	s, e := namedReturn()
	observe("an unwritten named return", "defined", fmt.Sprint(s, " ", e == nil))

	observe("a variadic parameter with no argument", "defined",
		fmt.Sprint("nil=", variadic() == nil, " length=", len(variadic())))

	source := []int{10, 20, 30, 40}
	observe("unwritten slice bounds", "defined", fmt.Sprint(source[:], " ", source[1:], " ", source[:2]))

	value, ok := m["missing"]
	observe("a nonexistent key returning two values", "defined", fmt.Sprint(value, " ", ok))

	text := "élan"
	observe("a string's byte instead of a character", "open",
		fmt.Sprint("length=", len(text), " first=", text[0], " runes=", len([]rune(text))))

	observe("unused variable", classifySource(`package trial
func F() { x := 1 }`), "")

	observe("unused import", classifySource(`package trial
import "fmt"
func F() {}`), "")

	observe("unwritten return value", classifySource(`package trial
func F() int { }`), "")

	observe("implicit type conversion", classifySource(`package trial
func F() { var a int32 = 1; var b int64 = a; _ = b }`), "")

	summarize()
}

func classifySource(source string) string {
	if compiles(source) {
		return "open"
	}
	return "rejected"
}
```

```
-- what stands in for the unwritten --
an integer with no initial value           defined     0
a slice with no initial value              defined     nil=true length=0 appendable=[1]
reading a map with no initial value        defined     0
writing to a map with no initial value     open        panic=true
reading a pointer with no initial value    open        panic=true
unwritten struct fields                    defined     "" 0 false
an unwritten named return                  defined     0 true
a variadic parameter with no argument      defined     nil=true length=0
unwritten slice bounds                     defined     [10 20 30 40] [20 30 40] [10 20]
a nonexistent key returning two values     defined     0 false
a string's byte instead of a character     open        length=5 first=195 runes=4
unused variable                            rejected
unused import                              rejected
unwritten return value                     rejected
implicit type conversion                   rejected
unwritten: 8 defined, 4 rejected, 3 open
```

The last four lines are the four rejections measured in detail in the first lesson; here they
sit as the completion of the list and fall into the same class (`rejected`). The remaining
eleven lines belong to this lesson and cover eight separate types: integer, slice, map,
pointer, struct, named return value, variadic parameter, string. Each line asks the same
question of a single type — what happens when it is left with no initial value — and delivers
the answer to `observe` in three parts: a name, a class, evidence. The evidence column carries
the real values that back up the claim; the next section reads these one by one.

#### Reading the Numbers

**First claim: there is no such thing as "uninitialized" in Go.** In **eight** of the fifteen
unwritten cases, the language puts something defined in place of the unwritten: an integer's
zero value is `0`, a slice's zero value is `nil` but its length is `0` and it is still
appendable, reading from a map returns `0`, a struct's fields carry their own zero values, an
unwritten named return returns `0` and `nil`, a variadic parameter given no argument becomes a
`nil` slice, unwritten slice bounds stretch from the start to the end, a nonexistent key
returns `0` and `false`. All eight are not a convenience but a contract: the reader knows what
they will find without looking at the source.

What these eight share is that the zero value looks different **per type** but is always
predictable. For an integer the zero value is numeric zero; for a string it is the empty
string (`""`); for a boolean it is `false`; for a pointer, a slice, a map, or a function it is
`nil`. All four are called by the same word (`nil`) without carrying the same idea, but all
four carry the same intent: "not yet bound to anything." `nil` itself is not a single value —
a pointer's `nil` means it points to no address in memory, a slice's `nil` means it has no
backing array, a map's `nil` means no storage has been allocated, a function's `nil` means
there is no code that can be called. All four print with the same word, but all four represent
a distinct absence within their own type.

**Second claim: all four things the compiler rejects are, in other languages, a warning level
issue.** Unused variable, unused import, an unwritten return value, implicit type conversion —
all four make the source fail to compile. Rejecting is not a show of strictness, it is a
decision to **narrow** what stands in for the unwritten: the language chooses, in these four
cases, "never allow it" over "put something defined there."

Compared with the first eight, these four show an interesting reversal. In the first eight, the
language lets the source run despite something unwritten, and fills the gap itself. In these
four, instead of filling the gap, the language never lets the source run at all. Both are "what
stands in for the unwritten," but one is a value carried into runtime, the other is a door
closed at compile time.

**Third claim: definedness works in the reading direction and stops in the writing and
interpreting direction.** All three of the open cases show this. **Reading** from a map with no
initial value is defined, but **writing** to it stops the program — a panic. A pointer with no
initial value is a defined value (`nil`), but **reading** it stops the program — again a
panic. A string's length is defined, but what indexing into it gives is not a character, it is
a **byte** — `"élan"` is five bytes, four runes, and the first byte is `195` (the first part of
a multi-byte encoding, not a letter on its own).

#### Limiting Measurement: "Defined" Does Not Mean Usable

Not all eight defined lines are equally usable. Comparing the second line (`nil` slice) with
the third line (reading from a `nil` map) makes the difference clear: both are left with no
initial value, and both are `nil`. But `append` works on a `nil` slice — the output shows
`appendable=[1]` — while writing to a `nil` map panics. The same word (`nil`), the same class
(`defined`), two different results.

**LB15.** The difference is not in `nil` itself, it is in the **separate contract** the
language gives each of these two types. A slice, even when `nil`, is a header that carries a
length (`0`) and a capacity (`0`); `append` can allocate a new backing array for that header and
grow, because growing is a behavior a slice already has. A map, when `nil`, contains no storage
at all; reading can cope with that emptiness (the key being looked for does not exist at all,
so its zero value is returned), but writing requires allocating room for a new entry, and a
`nil` map has nowhere to allocate it. The word "defined" is correct in both, but **what** is
defined changes from type to type.

This is a warning worth keeping in mind while reading the eight defined lines: a value having
a zero value does not mean that value can withstand **every** operation. Which operations work
with a zero value will be measured again when this course's next topic treats slice and map
types on their own.

The same limiting reading appears once more in the second open line. A pointer's zero value is
also `nil`, and like a `nil` slice, it withstands some operations — comparing a `nil` pointer
against another pointer, or reassigning it, works without a problem. But reading a field
(`pointer.Number`) does not work, because reading a field first requires a struct instance to
exist, and a `nil` pointer points to no instance at all. Three types — slice, map, pointer —
all three carry `nil` as their zero value, but all three define their own set of behaviors;
there is no single "`nil` rule," each type carries its own separate contract.

#### Blind Spot: The Rejection Reason Is Not Measured

The four rejection lines rely on `compiles`'s `true`/`false` answer, and the error text is
never read. This has a cost: a source could have been rejected for a reason **other** than the
one being measured, and the measurement cannot tell the difference. This is why the rule holds
— every rejected piece of source must be compilable if not for exactly one unwritten thing; in
the unused-variable example there is nothing wrong besides `x := 1`, in the return-value
example there is nothing wrong besides `func F() int { }`. This condition was set up and
verified by hand; the measurement tool does not check it on its own.

**LB16.** This is a deliberate limitation coming from the measurement's method, not an
oversight. `compiles` only reads `cmd.Run()`'s exit status; not reading the error text protects
both the ban on version-dependent data and the "what, not why" question. The cost is that the
four sources in this lesson had to be verified **by hand** — each was written by trying exactly
one change and checking that everything else stayed fixed. The same discipline continues
everywhere in the rest of the course wherever a compiler rejection is measured.

The eight-line defined group has a similar blind spot: the `observe` function prints the
evidence it is given as is, it does not test its correctness. Whether a line truly belongs to
the "defined" class is decided by hand while the code is being written — the frame only makes
that decision **countable**, it does not produce it itself. The oracle — the knowledge of which
unwritten case falls into which class — sits outside the program, in the hands of whoever wrote
this lesson.

#### Summary

- Of fifteen unwritten cases, **8 are defined, 4 are rejected, 3 are left open** — the course's
  binding split was produced here.
- There is no such thing as "uninitialized" in Go: each of the eight defined lines carries a
  concrete value the language puts in place of the unwritten.
- The four things the compiler rejects stop compilation by a decision to narrow what stands in
  for the unwritten, even though all four are harmless at runtime.
- Definedness works in the reading direction and stops in the writing and interpreting
  direction: reading from a `nil` map is defined, writing panics; a string's length is defined,
  indexing gives a byte.
- A `nil` slice and a `nil` map carry the same word but do not withstand the same operations —
  a `nil` slice can be appended to, a `nil` map cannot be written to.

#### Next Step

Zero values measured what happens when a variable is **never touched at all**. But a program
almost never stays untouched — it branches, it loops, it moves from one state to another. The
next lesson turns to control flow: why can `for` be written without a condition, why can
`switch` be written without an expression, and what stands in for the `break` left unwritten at
the end of a branch?

### Control Flow

Source: https://academia.sh/en/courses/go-fundamentals/control-flow

Five forms are measured with the same question — unconditional for, single-condition for, expressionless switch, an early return without an else, a switch branch that does not fall through on its own — and all five fill the unwritten part with a defined counterpart. Limiting measurement: an unwritten break is not needed, but code that wants to fall through has to write an extra word (fallthrough).

The previous lesson measured fifteen unwritten cases, but none of them sat **inside** a control
structure — all of them were about the absence of a variable, a return, or an argument. This
lesson carries the same question into control flow: why can `for` sometimes be written without
a condition, why can `switch` sometimes be written without an expression, and what stands in
for a `break` left unwritten at the end of a branch?

The Programming Foundations course (M01/K02) built conditional branching and loop control using
Python; there, loops and branching had separate keywords (`while`, `for`, `if`/`elif`/`else`)
and each had its own fixed syntax. **These concepts are not rebuilt here** — one sentence is
enough to state the difference: Go uses a **single** keyword for loops (`for`), and which
classic loop form that word corresponds to depends on which parts of the source are written and
which are left unwritten. This lesson measures that dependency.

#### Five Forms, One Question

Go's control flow rests on two keywords: `for` and `switch` (`if` is a third, but the only
thing left unwritten there is `else`). The distinction other languages carry with separate
words — "infinite loop," "conditional loop," "counted loop" — is reduced in Go to **which parts
of a single `for`** are written. We run five separate forms through the same frame: each leaves
something unwritten, and the language fills that gap on its own.

```go
// flow.go — five forms, each filling an unwritten part with a defined counterpart
package main

import "fmt"

func classify(x int) string {
	if x < 0 {
		return "negative"
	}
	return "not negative"
}

func main() {
	counter := 0
	for {
		counter++
		if counter == 5 {
			break
		}
	}
	fmt.Printf("%-33s %s\n", "unconditional for (until stop)", fmt.Sprint("counter=", counter))

	i, total := 0, 0
	for i < 5 {
		total += i
		i++
	}
	fmt.Printf("%-33s %s\n", "single-condition for", fmt.Sprint("total=", total, " final_i=", i))

	score := 72
	var grade string
	switch {
	case score >= 90:
		grade = "AA"
	case score >= 70:
		grade = "BA"
	default:
		grade = "FF"
	}
	fmt.Printf("%-33s %s\n", "expressionless switch", fmt.Sprint("grade=", grade))

	fmt.Printf("%-33s %s\n", "early return without else", fmt.Sprint(classify(-3), " / ", classify(3)))

	result := ""
	switch 2 {
	case 1:
		result += "one"
	case 2:
		result += "two"
	case 3:
		result += "three"
	}
	fmt.Printf("%-33s %s\n", "switch does not fall through", fmt.Sprint("result=", result))
}
```

```
unconditional for (until stop)    counter=5
single-condition for              total=10 final_i=5
expressionless switch             grade=BA
early return without else         negative / not negative
switch does not fall through      result=two
```

Each of the five lines rests on a number that can be read independently of its own body.
`total=10` is the sum of `0+1+2+3+4`, confirming the loop ran five times (`i` from zero to
four) and kept going as long as the `i < 5` condition held; `final_i=5` shows the loop stopped
the instant that condition stopped holding. `grade=BA` proves the score of 72 fell into the
`score >= 70` branch and the `score >= 90` branch was never tried at all. All five are
independent proofs of one another, but every one answers the same question: what fills in the
part that was not written?

**LB17.** All three parts of `for` (initialization, condition, post-statement) are optional. In
the first line none of them is written; the compiler puts an **implicit `true`** in place of
the unwritten condition, which is why the loop does not stop on its own — `counter` shows it
would keep growing without bound until `break`, and we stop it at five ourselves. In the second
line, only the condition is written; when initialization and the post-statement are left
unwritten, the language skips them outright, and it is enough that `i` was defined before the
loop and incremented by hand inside the body.

Both `for` forms show the same claim from opposite ends: an unwritten part is met by the
language saying "consider this part absent" — with no condition it is always taken as true,
with no initialization or post-statement they are never run at all. Both belong to the same
`for` keyword; which parts are written decides which classic form (infinite, conditional,
three-step) the loop corresponds to.

This means `for` carries a single syntax tree, but all three branches of that tree are
optional. The three-step form (`for i := 0; i < 5; i++ { }`) skips no part and stands in its
most explicit shape in the source; the single-condition form in the second line moves
initialization and the post-statement outside, before and inside the body —
`i, total := 0, 0` before the loop, `i++` on the body's last line. For the compiler these two
forms produce the same machine code; the only difference is entirely what the reader sees at a
glance. In the three-step form, the loop's bounds gather into a single line; in the
single-condition form, seeing the bounds requires reading the whole body.

This distinction has a cost: in the first line, `counter` is reset before the loop and
increases inside it, but where the loop stops is written neither in a header nor on a single
line — it is hidden in an `if` block in the middle of the body. The unconditional `for` is the
most flexible form because it pins the stopping condition **nowhere**; that flexibility
requires reading the entire body to find the stopping condition.

This flexibility has a reason as much as a cost. Some loops have a stopping condition too
complex to express with a single comparison — it depends on more than one condition holding
together, or on a signal arriving from an outside source (a channel, a reader). For a loop like
this, writing a single condition in the header would mean trying to **squeeze** the body's real
stopping logic into the header; the unconditional `for` is the way to leave that stopping logic
in the body as is. The three-step and single-condition forms are preferred when the stopping
condition can be reduced to a single comparison; the unconditional form, when it cannot.

**LB18.** In the third line, `switch` is written without taking an expression. When a `switch`
expression is not written, the compiler compares it against an **implicit `true`**; each `case`
in turn becomes a logical expression of its own. Since `score >= 70` is true, `grade` becomes
`"BA"` — exactly what an `if`/`else if` chain would do, only with `switch` syntax.

This shows that `switch` in Go is less an "equality" construct and more a "branching" one. In
some languages, `switch` can only compare a single value against constant values; in Go, when
no expression is written, `case`s can carry free-form logical conditions, and those conditions
are tried in order, with the first true one running — exactly as in an `if`/`else if` chain.
There is no difference at all between writing `switch { }` and writing `switch true { }`; the
second only makes explicit the part the first leaves unwritten.

This shows that `switch` merges two separate uses into a single syntax. When an expression is
written (`switch score { case 90: ... }`), each `case` can carry a list of constants
**compared for equality** against that expression — multiple-value matching is one example of
this, and it will reappear later in the course. When no expression is written, each `case`
instead carries a complete logical expression of its own, and what is tested is not equality
but truth. The same keyword turns into two different branching forms depending on the single
part that is written — the expression itself.

In the fifth line, a different unwritten case is measured: no `case` ends with `break`, yet
`result` carries only the value of the matching branch (`case 2`) — `case 3`'s body never runs
at all. In Go, the end of a `case` means the branch **closes** on its own; what stands in for
the unwritten `break` is that falling through **never happens at all**.

The fourth line comes from a different family: with no `else` ever written, the `if` block ends
with a `return`, and the remaining code runs as though it were an `else` block.
`classify(-3)` enters the `if` branch and returns `"negative"`; `classify(3)` never enters the
`if` branch and falls through to the second line, returning `"not negative"`. What stands in
for the unwritten `else` is that every path leaving the `if` block — whether by a `return` or
by never entering at all — **arrives** at the same place, the rest of the function.

This form is a pattern known in Go sources as a "guard clause" — a way to filter out
exceptional cases early with a `return` at the top of a function, so that the rest of the body
can be written without one more level of nesting. Had an `else` followed the line
`if x < 0 { return "negative" }`, the rest of the function would have shifted one level deeper;
because no `else` is written, the second `return` stays at the same indentation level as the
function's main body. What stands in for the unwritten `else` is, for this reason, not only a
behavior but also a gain in **form**: the body reads as a flat sequence instead of nested
blocks.

This pattern becomes even more visible when a function filters out more than one exceptional
case. When several guard clauses are written one after another (`if a { return }`,
`if b { return }`, `if c { return }`), each writing its own `else` would push the body one
level deeper on every line; with no `else` ever written, all the guard clauses sit side by side
at the same indentation, and the function's "real work" stays at the end, at the least
indented line. The unwritten `else`, here, affects the readability not of a single line but of
the **whole** function.

#### Limiting Measurement: An Unwritten Break, a Fallthrough That Must Be Written

The "no fall-through" behavior in the fifth line looks like a convenience — we were spared
writing one word (`break`). But when we want the reverse — a branch deliberately falling
through into the next one — the language does not give that **for free**.

The importance of this measurement lies in the answer it gives to the question "is an unwritten
thing always the shortest path." As we saw in the fifth line, no `case` wrote `break`, and yet
only the matching branch ran — that looked like a gain. But this gain does not mean "falling
through" never exists in the language; it only means it is **not the default**. The following
two `switch` statements carry the same three branches and test the same value (`2`); the only
difference between them is that the second adds one word to the `case 2` body.

```go
// limiter.go — an unwritten break costs nothing, but wanting to fall through costs a word (fallthrough)
package main

import "fmt"

func main() {
	notWritten := ""
	switch 2 {
	case 1:
		notWritten += "one"
	case 2:
		notWritten += "two"
	case 3:
		notWritten += "three"
	}

	written := ""
	switch 2 {
	case 1:
		written += "one"
	case 2:
		written += "two"
		fallthrough
	case 3:
		written += "three"
	}

	fmt.Printf("%-28s %s\n", "break not written", fmt.Sprint("result=", notWritten))
	fmt.Printf("%-28s %s\n", "fallthrough written", fmt.Sprint("result=", written))
}
```

```
break not written            result=two
fallthrough written          result=twothree
```

**LB19.** Both `switch` statements test the same value (`2`) and carry the same three
branches; the only difference is that the second `switch` adds the `fallthrough` word to its
`case 2` body. In the first line the result is only `"two"` — `case 2` runs, `case 3` is never
reached at all. In the second line the result is `"twothree"` — a deliberately requested
fall-through runs `case 3` right after `case 2`.

This adds an asymmetry to the question of "what stands in for the unwritten." The default
behavior (not falling through) needs no word at all; the **opposite** of the default (falling
through) needs a named, single-purpose keyword. This asymmetry is deliberate: someone reading
the body of a `case`, as long as they do not see `fallthrough` there, can be sure that branch
ends on its own. What is unwritten here is not a gap, it is a **guarantee** — only one reading
remains valid: the branch closes on its own unless stated otherwise.

`fallthrough` carries one more restriction: it can only fall into the **next** `case`, without
testing that case's own condition. In the second `switch`, `case 3`'s own condition (being
equal to `3`) is never tested at all — the moment `fallthrough` is seen, the next branch runs
unconditionally. This shows that `fallthrough` should not be confused with **retesting a
case's condition**: `fallthrough` does not mean "test the next branch's condition too," it
means "run the next branch's **body** too." This is a detail that needs to be told apart at
writing time — someone who does not know that `fallthrough` runs the next branch without
testing it might assume the falling-through branch's condition gets checked too.

`fallthrough` comes with a restriction of its own: it can only be the **last** statement in a
`case` body. Writing it in the middle of a branch would be rejected by the compiler — falling
through is a single transition that comes after the rest of the branch has run, not a branching
tool that can be scattered inside the branch. This restriction sets `fallthrough` apart from an
ordinary `goto`: it can only move to an **adjacent** next branch, and it can only appear at the
very end of a branch.

This asymmetry resembles, but sits in a different place from, the "rejecting does not mean
writing less" observation measured in the course's first lesson. There, the counterpart of a
rejected pattern was a longer piece of writing; here nothing is rejected, it is only that the
**non-default** path is written longer. The default behavior always takes the shortest
writing; the non-default behavior always asks for one more word.

Looking at all five forms together, a shared pattern emerges: in Go's control flow, every
unwritten part stands in for the **most commonly wanted** behavior. A loop running forever is
rarely wanted, so the unconditional `for` needs no special syntax — the mere absence of a
condition is enough. A `switch` branch falling into the next one is rarely wanted, so not
falling through is the default; wanting to fall through means this exceptional request must be
flagged on its own. Code continuing after an `if` block is commonly wanted, so it continues
even with no `else` written. What is left unwritten is always the **majority's** path; what
must be written is always the **minority's** exception.

It is also worth noting that all five forms can be run side by side in a single measurement
program: none affects another, none needs any special setup. This shows that control flow in
Go was designed as a set of small, independent rules — understanding one form does not require
knowing the others; each answers, on its own, a single unwritten case with a single answer.

#### Summary

- All three parts of `for` are optional; an unwritten condition becomes an implicit `true`, an
  unwritten initialization and post-statement are skipped outright.
- An expressionless `switch` is compared against an implicit `true`, and each `case` becomes a
  logical expression of its own.
- When an `if` block ends with a `return`, the rest of the code runs like an `else` block even
  with no `else` written.
- A `switch` branch closes on its own with no need for an unwritten `break`; all five forms fill
  their gap with a defined counterpart.
- Wanting the opposite of the default (falling through) does not come free: the `fallthrough`
  word has to be written explicitly.

#### Next Step

The "early return without else" form measured in this lesson showed cases where a function
ends **early**, but the function's return value itself was never questioned — `classify`
always returned a single string, and no one ever left that string unwritten. The next lesson
asks that: when a function returns more than one value, what happens if one of them is left
unwritten, and what value does a named return carry when it is left completely untouched?

### Functions

Source: https://academia.sh/en/courses/go-fundamentals/functions

A named return value left completely untouched carries its type's zero value; a function never writing a return value at all is rejected at compile time, but a caller ignoring what comes back without ever catching it says nothing at all — since error is itself just a return value in Go, this open behavior is the course's most important example. Limiting measurement: once a return value is captured into a variable and left unused, this openness closes and the unused-variable rejection takes over.

The previous lesson measured the "early return without else" form but never once looked at
what the function actually returned. This lesson looks at that value: what happens when a
function returns more than one value and one of them is left unwritten, what value does a
named return carry when it is left completely untouched, and what does the language say when a
caller never catches a returned value at all?

The Programming Foundations course (M01/K02) built function definition using Python; there, a
function returned a single value or none at all, and more than one value was packed into a
tuple and passed through a single return slot. **That concept is not rebuilt here** — one
sentence is enough to state the difference: in Go, multiple return is part of the **signature
itself**; it is not packed into a tuple, each value is written separately with its own type.
This lesson measures what these separate slots carry when they are left unwritten.

#### Multiple Return Values and the Zero of a Named Return

In Go, a function can return more than one value, and these values can be **named** — the
names written in the signature can be used as ordinary variables inside the function's body.
The `divide` function shows this: `result` and `err` are named in the `func` line, and the
body returns them with a bare `return`, never writing `return result, err`.

The `namedReturn` function measured in the Zero Values lesson already showed this behavior in
a single line: `func namedReturn() (result int, err error) { return }` returned two zero
values without running any body at all. This lesson measures the same mechanism in a function
whose body really does run and branches based on a condition — `divide` can take two different
paths, and how much of the named return is touched changes on each path.

```go
// function.go — multiple return values and a named return returning zero when unwritten
package main

import "fmt"

func divide(a, b int) (result int, err error) {
	if b == 0 {
		err = fmt.Errorf("division by zero")
		return
	}
	result = a / b
	return
}

func main() {
	r1, e1 := divide(10, 2)
	fmt.Printf("%-24s result=%v err=%v\n", "successful division", r1, e1)

	r2, e2 := divide(10, 0)
	fmt.Printf("%-24s result=%v err=%v\n", "division by zero", r2, e2)

	divide(10, 0)
	fmt.Printf("%-24s %s\n", "ignored call", "no panic, program continues")
}
```

```
successful division      result=5 err=<nil>
division by zero         result=0 err=division by zero
ignored call             no panic, program continues
```

**LB20.** In the `b == 0` branch, only `err` is assigned a value; `result` is never touched at
all. The printed `result=0` comes out even though the source has no line anywhere reading
`result = 0` — `int`'s zero value is a direct extension of the frame measured in the Zero
Values lesson. A named return value behaves, from the moment the function starts, like a local
variable **pre-initialized** with its own type's zero value; if the body never touches it, what
it returns is that initial value.

This initialization is refreshed **every single time** the function is called. `divide(10, 2)`
and `divide(10, 0)` are two separate calls to the same function, and in both `result` starts
from zero; in the first call it is assigned `5`, in the second it is never assigned and stays
zero. The previous call's `result` has no effect at all on the next call — every call starts
its named returns fresh, with their own zero values.

The second line makes this even clearer: because `err` is assigned, `err=division by zero`
appears, yet `result` is still `0`. Both values return with the same `return` statement, but
only one was written by hand in the source. A named return opens a way to write **some** of a
multiple return value's values and leave the rest at their zero value — even though two names
appear in the signature, the body is not required to touch both.

The bare `return` (instead of `return result, err`) here is more than shorthand. In an
unnamed multiple return, every `return` statement has to list the values to be returned
explicitly; once named, it becomes possible to return the function's current state as is,
without writing the names twice in the source. This shows that `result` and `err` behave, for
the whole function, like **local, not package-level**, variables — even though they are
declared in the signature, they can be read and changed anywhere in the body.

Naming also has a documentation side. Had `func divide(a, b int) (int, error)` been written,
the signature would only declare two types; understanding which value carries the result and
which carries the error would require reading the body. Writing the names `result` and `err`
in the signature gives that information without going down into the body — the signature says,
on its own, which return slot carries what. This is why named returns work in exactly the
opposite direction from short declaration: short declaration **removed** the type name; a
named return **adds** a name of meaning.

This documentation gain should be read together with naming being **not mandatory**.
`func divide(a, b int) (int, error)` is also a valid signature and returns exactly the same two
values in exactly the same way; naming is chosen only for readability, it does not change the
behavior. Whether a function needs a named return does not depend on how many values it
returns, it depends on how clearly the meaning of those values already reads from the
signature.

#### An Unwritten Return Value Is Rejected, an Unused One Is Not

One of the four rejections measured in the first lesson was the return value being **left
unwritten**: the body of `func F() int { }` ended without ever producing a value, and the
compiler rejected it. This lesson measures the exact opposite of that rejection: is the
**caller** never using the return value rejected the same way? Intuition might say "no" — a
call being made and its result not used is, in many contexts, entirely ordinary (a log call's
return value is usually unimportant). This lesson measures the real behavior, not the
intuition.

The third line answers this by running it. The call `divide(10, 0)` is written on its own, as
a single expression, without being assigned to anything — both return values (the result and
the error) are silently dropped. The program does not panic, the compiler says nothing at all;
execution continues without interruption.

**LB21.** No variable at all is defined on this line — only a call is made. The
"unused variable" rejection measured in the first lesson applies to a name that is defined and
never read; here no name is defined at all, so that rule never comes into play. The two
returned values vanish, along with the call itself, without ever being written anywhere.

Read together with the four rejections in the first lesson, this reveals a striking asymmetry.
A body like `func F() int { }` was rejected because it did not **write** its return value — the
compiler would not accept a body that fails to keep the promise its signature made. But the
call `divide(10, 0)` **produces** its return value in full — the function keeps its promise,
both values really are computed — only the caller never asks for these produced values at all.
The compiler's concern stops at production itself; what happens to a produced value afterward
is a matter that lies outside the function's boundary.

This is the most important example so far of the course's **open** class, because in Go an
error is an ordinary **return value** — there is no separate mechanism, no exception type, no
mandatory catch construct. If a function returns an `error`, whether to read that value or not
is entirely the caller's decision. The language does not require, **in any way**, that a
returned error be checked; the error returned by `divide(10, 0)` vanishes as silently as an
event that was never logged.

This behavior leaves no trace in the source either. The third line's output ("no panic,
program continues") confirms this: the "division by zero" error really is produced, but since
it is never written anywhere, it has no effect at all — no panic, no log line, no change in
exit code. Someone reading the source cannot even tell from the `divide(10, 0)` line whether
that call produced an error at all; understanding that requires reading the function's
**definition**. This is the shared trait of open behavior: seeing the result is not enough from
the call line, the definition itself has to be read.

#### Limiting Measurement: What Is Ignored Does Not Always Stay Open

This openness has a limit. Ignoring returned values by **never catching them at all** is free,
but capturing them into a variable and then not using them is not free. To measure this, we
return to the first lesson's compiler call and call `divide` in two separate pieces of source —
one never catching the results, the other catching them and using only one.

```go
// compiler.go — helper that calls the compiler from inside the program
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
}

func classify(ok bool) string {
	if ok {
		return "compiled"
	}
	return "not compiled"
}
```

```go
// trial.go — fully ignoring is not the same as capturing and half-using
package main

import "fmt"

func main() {
	fullyIgnored := `package trial
import "fmt"
func divide(a, b int) (result int, err error) {
	if b == 0 {
		err = fmt.Errorf("division by zero")
		return
	}
	result = a / b
	return
}
func F() { divide(10, 2) }`

	halfIgnored := `package trial
import "fmt"
func divide(a, b int) (result int, err error) {
	if b == 0 {
		err = fmt.Errorf("division by zero")
		return
	}
	result = a / b
	return
}
func F() { r, e := divide(10, 2); _ = r }`

	fmt.Printf("%-34s %s\n", "fully ignored", classify(compiles(fullyIgnored)))
	fmt.Printf("%-34s %s\n", "captured and half used", classify(compiles(halfIgnored)))
}
```

```
fully ignored                      compiled
captured and half used             not compiled
```

**LB22.** Both sources call the same `divide` function and ignore the same result (the error);
the only difference is that the second source **captures** the returned values into variables
named `r` and `e`. In the first source no name is defined at all, so the unused-variable
rejection never comes into play. In the second source, `e` is defined and never read — `_ = r`
only rescues `r`, leaving `e` an unused variable, and compilation is rejected.

This sharpens the boundary of the `open` class. Never catching a return value is a decision the
language can never see at all — no name appears on the call line, there is nothing to check.
But once it is bound to a name, that name falls under the scope of the rule measured in the
first lesson: every written name must either be used or never written in the source at all.
The error itself stays `open` — the language still does not force you to read it — but binding
it to a **name** and then forgetting it runs into a different rule.

This distinction also explains why error checking in Go is most often written in the form
`result, err := divide(...)`. The moment `err` is captured into a variable, leaving it unread
stops compilation — writing `if err != nil` is not a style preference, it is the most natural
way to count the name `err` as **used**. Ignoring a return value by never catching it at all is
still possible, but once it is caught, ignoring it is no longer possible.

This also carries a signal for someone reading the source: if a call's result was never
caught, that call's error was certainly never checked — but if a result was caught, it is also
certain that result was used **at least enough to compile**. Using a value does not mean
checking it — `_ = e` could also have been written and `e` never read, and would still count as
"used" — but it at least shows the name stood in the source for a purpose. This is a third use
of the blank identifier measured in the first lesson: writing `_ = e` is the written form of "I
know this error but I am deliberately not checking it." The unused-variable rejection does not
force checking an error — it only refuses to let it be **named and forgotten**.

The difference looks small but matters in practice: finding which errors were never caught at
all requires scanning the call lines across a codebase (the compiler leaves no trace here at
all), but finding which caught errors went unchecked is somewhat easier — at least a deliberate
disregard like `_ = err` can be searched for. The compiler cannot see the first group at all,
and sees the second group only through the question "was it unused," never through "was it
checked."

This lesson's three measurements — the zero of a named return, the rejection of an unwritten
return, the openness of an unused return — show all three of the course's classes on a single
function. `divide` itself never changes; what changes is **how we call it**. The same function
carries a defined zero value when called with a named return, stops compilation when left
without a signature, and disappears silently when left uncaught at the call site. For the first
time in this lesson, what stands in for the unwritten depends not on the function itself but on
**how it is called**.

Reading the three measurements together shows Go's function design repeating the same
principle three times: **production** is mandatory, **consumption** is not. A function must
produce every value its signature promises — either by writing it by hand or relying on a
named return's zero. But the caller **taking** that produced value is never something the
language forces. Only once it is taken does that taken thing also need to serve a purpose in
the source.

#### Summary

- If the body never touches a named return value, that value carries its type's zero value —
  `result=0` appears even though the source never writes `result = 0` anywhere.
- A return value being left entirely unwritten (`func F() int { }`) is rejected at compile
  time.
- A caller ignoring returned values without ever catching them is not rejected in any way —
  since error is a return value in Go, this is the course's most important example of open
  behavior.
- Once returned values are captured into a variable, the situation changes: the unused-variable
  rejection takes over, and a half-used capture no longer compiles.
- The error itself always stays open; binding it to a name and then forgetting it runs into a
  separate, different rule.
- The function design's principle is single: a function must produce every value it promises,
  but the caller taking that value is never something the language forces.

#### Next Step

In this lesson the `divide` function was always called with two fixed arguments, and what was
measured was the unwritten cases on the return side — there was no ambiguity at all on the
argument side, the signature said exactly how many arguments it wanted. But some functions do
not know in advance how many arguments they will take — the caller can pass zero, one, or
twenty values. The next lesson, the last lesson of this topic, measures that ambiguity: what
happens to a variadic parameter when it is given no argument at all, and what relationship does
this lesson's "name of a captured value" have with a slice itself?

### Variadic Functions

Source: https://academia.sh/en/courses/go-fundamentals/variadic-functions

A variadic parameter given no argument at all becomes a nil slice, arguments written one by one produce a fresh slice, and opening an existing slice with ... shares the same backing array without copying it — a change the callee makes is visible to the caller. Limiting measurement: a nil slice and an empty slice are the same length but diverge on == nil, and this difference becomes visible in their JSON counterpart.

The previous lesson measured that a return value has to be used once it is bound to a name. This
lesson carries the same question to the argument side: when a function does not know in advance
how many arguments it will take, what does that parameter carry when it is given no argument at
all, and what is the relationship between the two when an existing slice is passed to that
parameter by being **opened**?

The Programming Foundations course (M01/K02) built pass-by-value and pass-by-reference using
Python; there, passing a list into a function meant sharing the list itself. **That concept is
not rebuilt here** — one sentence is enough to state the difference: in Go everything is passed
by value, slices included; what this lesson measures is how the difference between "pass by
value" and "passing a value that points to a shared backing array" shows up in variadic
arguments.

#### Three Call Forms

In Go, putting `...` in front of a parameter makes that parameter accept a **variadic**
number of arguments, and inside the function body it behaves like a slice. The caller can call
it in three separate forms: giving no argument at all, listing arguments one by one, or opening
an existing slice already sitting in a variable with `...`. All three are valid syntax, but
they do not all produce the same thing behind the signature.

This course itself has been using variadic arguments from the very start: every
`fmt.Printf` and `fmt.Println` call is a call to a standard-library function taking a variadic
number of arguments. Being able to pass a single value in one call and five in another is
exactly thanks to the mechanism this lesson measures. This lesson asks how that same mechanism
behaves when we write our own functions with it.

Two helper functions measure this parameter's behavior from two directions: `properties`
returns its length and whether it is `nil`; `zeroFirst` changes its first element, letting us
test whether that change is visible to the caller.

```go
// helpers.go — two helpers for a variadic parameter
package main

func properties(numbers ...int) (length int, isNil bool) {
	return len(numbers), numbers == nil
}

func zeroFirst(numbers ...int) {
	if len(numbers) > 0 {
		numbers[0] = 0
	}
}
```

**LB23.** `properties`'s return signature uses the previous lesson's named return;
`numbers == nil` is the standard way to test whether a slice is `nil` — the same comparison
measured in the Zero Values lesson is run here over a parameter. `zeroFirst` also borrows a
technique from that same lesson: the `len(numbers) > 0` check exists so as not to panic by
indexing into a `nil` slice — reaching for a `nil` slice's zeroth element runs past the slice's
bounds, exactly as it would with an empty slice.

We run the three call forms side by side: with no argument at all, with arguments written one
by one, and with an existing slice opened with `...`.

```go
// variadic.go — no argument at all, a slice opened, an opened slice never being copied
package main

import "fmt"

func main() {
	l1, n1 := properties()
	fmt.Printf("%-36s length=%d nil=%v\n", "no argument given", l1, n1)

	l2, n2 := properties(1, 2, 3)
	fmt.Printf("%-36s length=%d nil=%v\n", "three arguments written one by one", l2, n2)

	source := []int{10, 20, 30}
	l3, n3 := properties(source...)
	fmt.Printf("%-36s length=%d nil=%v\n", "existing slice opened (...)", l3, n3)

	zeroFirst(1, 2, 3)
	fmt.Printf("%-36s %s\n", "did the one-by-one values change", "nothing observable, caller has no slice")

	zeroFirst(source...)
	fmt.Printf("%-36s source=%v\n", "did the opened slice change", source)
}
```

```
no argument given                    length=0 nil=true
three arguments written one by one   length=3 nil=false
existing slice opened (...)          length=3 nil=false
did the one-by-one values change     nothing observable, caller has no slice
did the opened slice change          source=[0 20 30]
```

**LB24.** The first three lines show what the same parameter carries across three different
call forms. Given no argument (`properties()`), the parameter is a `nil` slice — length `0`
and empty identity. When arguments are written one by one (`properties(1, 2, 3)`), the
compiler builds a **fresh slice** to hold these three values; this slice is not `nil`, because
something really was allocated somewhere. When an existing slice is opened with `...`
(`properties(source...)`), the length and nilness come out the same as the second line — but
this line's real difference shows up in the fourth and fifth lines.

The `nil` result in the first line confirms the pattern established in the first lesson: giving
no argument at all is not an error, it is a defined zero value. The second line shows, for the
first time, that it is not a variable but the **call itself** that produces a slice — writing
`1, 2, 3` builds a slice at the moment of the call, even though no slice literal
(`[]int{1, 2, 3}`) ever appears in the source.

Reading the first three lines by looking only at the `length` and `nil` columns, the second and
third lines cannot be told apart: both read `length=3 nil=false`. This shows a limit of the
measurement — the `properties` function itself has no way of knowing whether the slice passed
to it is **fresh** or **shared**, because that information does not show up directly in any of
the slice header's fields (length, capacity, pointer). Seeing this distinction needs a
different measurement, `zeroFirst`.

#### An Opened Slice Is Not Copied

This ambiguity reveals one more limit of the "defined" class. Each of the eight defined lines
in an earlier lesson gave a certain answer to a specific question; here the question is
different — it asks whether a function, by looking at its own parameter, can **know** how
connected that parameter is to its caller. The answer is no: the slice header carries no such
information, it can only be uncovered by observing behavior (changing it and watching the
result).

The fourth and fifth lines run the same function (`zeroFirst`) with two different call forms.
In the call `zeroFirst(1, 2, 3)`, the slice that gets changed is a fresh one built only for that
call — there is no variable on the caller's side corresponding to this slice at all, so the
question "did it change" does not even mean anything here. In the call `zeroFirst(source...)`,
the situation is different: `source` is `[10 20 30]` before the call, `[0 20 30]` after it.
Writing `numbers[0] = 0` inside the function changed the caller's own `source` variable.

**LB25.** This shows that a slice opened with `...` is **not copied**. Go does not allocate a
new backing array for the call `f(slice...)`; it passes `slice`'s own header (length, capacity,
pointer to the backing array) directly into the parameter. The parameter and `source` **share**
the same backing array; an element change made through the parameter becomes visible through
`source` as well, since it reads the same backing array.

This can look like it contradicts the "pass by value" rule (built as a concept in the
Programming Foundations course, not rebuilt here) — as if the slice were passing "by
reference." But what is passed is still a **value**: the slice header itself is copied, only
the backing array that header points to is not copied. The difference between
`zeroFirst(1, 2, 3)` and `zeroFirst(source...)` is not in the passing rule, it is in **which**
backing array is shared: in the first, the parameter points to its own fresh backing array that
no one else sees from anywhere else; in the second, the parameter points to `source`'s
**already existing** backing array, and `source` keeps seeing that same backing array too.

This distinction means that arguments written one by one are **always** safe, while an opened
slice **sometimes** carries a sharing risk. In a call like `sum(1, 2, 3)`, the function changing
its arguments never affects the caller, because there is no caller-side slice to share at all.
In a call like `sum(slice...)`, the function writing into its own parameter can also change
`slice` itself. This raises a question that needs asking while writing a function: if a
variadic function **writes** into its own parameter, that writing being able to affect the
caller too should be stated in the source — the signature does not show this risk on its own,
because `func zeroFirst(numbers ...int)` looks exactly the same for both the
`zeroFirst(1, 2, 3)` and `zeroFirst(source...)` calls.

This explains why almost none of the standard library's variadic functions **write** into their
own parameter: `fmt.Println` only reads its arguments, it never changes them. If a function you
are writing needs to write into its own variadic parameter, this lesson carries a warning: that
write can quietly leak into a slice the caller opened with `...`.

#### Limiting Measurement: A Nil Slice and an Empty Slice Are Not the Same

In the first line we saw a `nil` slice, in the second and third lines slices that were not
`nil`. But a slice that is not `nil` can also be **empty**. Comparing `properties()` with
`properties([]int{}...)` makes this difference clear: the first has no argument at all, the
second has one argument — that argument, built with `[]int{}`, is a slice with zero elements
that really does exist.

```go
// limiter.go — a nil slice and an empty slice are not the same thing
package main

import (
	"encoding/json"
	"fmt"
)

func main() {
	lNil, nNil := properties()
	lEmpty, nEmpty := properties([]int{}...)

	fmt.Printf("%-33s length=%d nil=%v\n", "no argument (nil slice)", lNil, nNil)
	fmt.Printf("%-33s length=%d nil=%v\n", "empty slice opened ([]int{}...)", lEmpty, nEmpty)

	var nilSlice []int
	emptySlice := []int{}

	bNil, _ := json.Marshal(nilSlice)
	bEmpty, _ := json.Marshal(emptySlice)
	fmt.Printf("%-33s json=%s\n", "nil slice's json counterpart", bNil)
	fmt.Printf("%-33s json=%s\n", "empty slice's json counterpart", bEmpty)
}
```

```
no argument (nil slice)           length=0 nil=true
empty slice opened ([]int{}...)   length=0 nil=false
nil slice's json counterpart      json=null
empty slice's json counterpart    json=[]
```

The first two lines show a difference `len` cannot tell apart: both have a length of `0`, but
one is `nil` and the other is not. `[]int{}`, even though it is empty, is a slice **allocated**
somewhere; it has no elements, but it has an identity. A `nil` slice, on the other hand, is
never allocated anywhere, and it has no identity either. `len` does not tell these two apart
because length counts how many elements are held, not whether something was allocated.

The difference shows up in a concrete place: the `encoding/json` package's converter. A `nil`
slice is written in JSON as `null`; an empty but non-`nil` slice is written as `[]`. Both carry
the information "there are no elements at all," yet the converter treats these two cases as
**separate** counterparts — one meaning "there is no value," the other "there is a value but it
is empty." This choice made by the standard library turns the difference between `nil` and an
empty slice from a mere language rule into a result visible in a real program's output.

This pair of counterparts also contrasts with the same two slices behaving in an entirely
identical way in a different context. When walked with a `for range` loop, there is no
difference at all between a `nil` slice and an empty slice — both finish the loop without ever
iterating. When `append` is called, both again behave the same — elements can be added to
either. The difference only shows up when **identity** is asked (`== nil`) or when
**identity-sensitive** code (like `encoding/json`) comes into play. This shows that the
`defined` class carries one more internal distinction of its own: some operations look at a
slice's identity, some look only at its content.

This adds one more limit to the course's zero-value measurement. A `nil` slice carries a
defined zero value, as measured in the Zero Values lesson, and is open to operations like
`append`. But being `nil` is not the same thing as "being empty," and this difference can
surface not only in an `== nil` comparison but also in other code that processes the slice
(here, in a converter). Whether a function knows it received no argument at all (`nil`) or was
called with an empty slice (`[]int{}...`) can change the behavior of the callee.

At the end of eight lessons, we can return to the question asked at the opening: how does a
language decide what stands in for the thing you did not write? This topic asked this question in
six separate places: what is required for a source to compile, what a variable's type is
inferred to be, what fills a constant sequence, what part of a control structure is skipped, what
happens when a return value is unwritten, and now, what happens when a parameter is given no
argument at all. The answer was never uniform: sometimes the language put a defined value in
place, sometimes the compiler never accepted the source at all, sometimes the result was left
to runtime. The 8/4/3 split measured in the Zero Values lesson showed that none of these three
answers dominates — all three are part of the language's design.

The `nil` slice measured in this lesson is the **most visible** member of that split, because
almost every operation on a slice (`len`, `append`, `for range`) absorbs the `nil` case without
friction — a programmer could work with a `nil` slice for days and never notice. This
invisibility ends, exactly as this lesson showed, at one boundary: the moment identity is
asked, or identity-sensitive code comes into play, being `nil` suddenly becomes visible.

Throughout this topic's eight lessons, the measurement followed the same order every time: put two
cases side by side, run the difference to show it, then find where that very difference itself
breaks down. This last lesson carries the same order too — three call forms were run side by
side, the difference between them (sharing) was measured, and even that difference was found to
have its own limit (the distinction between nil and empty). The next topic carries the same
order from a single function's parameter to an entire family of types.

#### Summary

- If a variadic parameter is given no argument at all, the parameter becomes a `nil` slice.
- When arguments are written one by one, the compiler builds a fresh slice; this slice is not
  `nil`.
- When an existing slice is opened with `...`, it is not copied — the parameter shares the same
  backing array as the caller's slice, and the callee's change is visible to the caller.
- A `nil` slice and an empty slice are the same in terms of `len` (`0`) but diverge in terms of
  `== nil`.
- This difference does not stay confined to a comparison: the `encoding/json` package writes a
  `nil` slice as `null`, an empty slice as `[]`.
- Operations like `for range` and `append` do not tell a `nil` slice apart from an empty one;
  the difference only shows up in identity-sensitive operations (`== nil`, `encoding/json`).

#### Next Step

A parameter given no argument at all becoming a `nil` slice is the most visible consequence of
zero values seen in this course so far. This topic closes here; the next topic looks at the type
itself — how do a slice, a map, and a struct meet the unwritten, and when a value is assigned
to a variable, is what results a **copy**, or, as we saw in this lesson, a **shared**
backing array?

## Composite Types

The array's fixed length and the slice's length-capacity pair and growth behavior, the copying traps of the shared underlying structure in slice operations, a map's missing key giving two answers at once, struct fields' order and embedding, the consequences of taking an address and choosing between a value and a pointer receiver, and the distinction between a string being a byte array and a rune.

### Arrays and Slices

Source: https://academia.sh/en/courses/go-fundamentals/arrays-and-slices

The same assignment line produces a copy in an array and sharing in a slice: an array's length is part of its type, and all elements are copied when it is assigned, while assigning a slice copies only the header and shares the backing array with the source. The limiting measure shows that a slice is also a value: sharing is cut off once an append call exceeds capacity, because the copied header now points to a separate backing array.

The Variadic Arguments lesson measured that a parameter given no argument at all is a `nil`
slice — one of the zero value's most visible consequences. This lesson returns to that same
type itself: how a slice, a map and a struct answer the unwritten, and a question never asked
before — when you assign a value to a variable, does a copy result, or sharing?

The Data Structures course established the array and the list as abstract data types and
compared implementation choices by complexity; that comparison is not repeated here. What is
measured here is the **observable** behavior of Go's own two types — array and slice — during
assignment: the same `=` sign does two different things depending on the type of the value on
the right.

Array, slice, map and struct are referred to in this course by the common name **composite
type**, because all four are built from other types: an array from `int`s, a struct from named
fields. The difference among composite types lies in how much gets copied during assignment —
and this lesson begins with the two most basic members of that difference, the array and the
slice.

#### Array: Fixed Length and Copied Assignment

In Go, an array's length is part of its type; `[4]int` and `[3]int` are two separate types,
just as `int` and `string` are two separate types. This is not a loose description like "an
array of four elements," it is exact information the compiler reads from the syntax.

**CT1.** — That a source directly assigning between two array types of different length fails
to compile is not run here with a compiler call; the measurement instead shows the same
information indirectly with the `%T` formatting verb — the printed type name itself carries
the length.

```go
// composite.go — array assignment copies, the type name carries the length
package main

import "fmt"

func main() {
	sourceArray := [4]int{10, 20, 30, 40}
	copyArray := sourceArray
	copyArray[0] = 99
	fmt.Println("type         :", fmt.Sprintf("%T", sourceArray))
	fmt.Println("sourceArray  :", sourceArray)
	fmt.Println("copyArray    :", copyArray)
}
```

```
type         : [4]int
sourceArray  : [10 20 30 40]
copyArray    : [99 20 30 40]
```

Had `var copyArray [3]int` been written right above the line `copyArray := sourceArray` in the
same source, compilation would stop right there; the assignment would be rejected because
`[3]int` and `[4]int` are two types that do not substitute for each other. This lesson does
not show this with a separate compiler call, because the Go's Design Goals lesson already
measured the rejection of implicit type conversion; the difference here is only that length,
too, is part of type identity.

The printed type is `[4]int`, not just `int` — the length stands inside the type name. After
the line `copyArray := sourceArray`, changing the first element affects only `copyArray`;
`sourceArray` stays unchanged. When an array is assigned, **all** four integers are copied to
a new variable — the syntax is a single equals sign, but underneath it there are four separate
copy operations.

This copying works not only in variable-to-variable assignment, but also when an argument is
passed to a function; parameters are assignments too. Another consequence of length being part
of the type follows from this: two arrays, as long as their element types are comparable, can
be compared directly with `==` — because what gets compared is not a pointer, it is the
array's **content itself**. The same property also explains why an array can be used as a map
key; as will be seen in the Maps lesson, a map's key type must be comparable, and an array
satisfies this condition naturally. The same rule works downward too: a struct with a slice
field also becomes non-comparable, because a struct's comparability depends on its fields'
comparability — this will come up again in the Structs lesson.

#### Slice: Length, Capacity and the Shared Backing Array

A slice is not an array; the slice itself is a small structure made of three parts: a pointer
to a backing array, the current length, and the capacity. The notation
`[]int{10, 20, 30, 40}` sets up all three at once — it produces a four-element backing array
behind it, and the slice points to that backing array.

```go
// sharing.go — slice assignment copies the header, the backing array is shared
package main

import "fmt"

func main() {
	sourceSlice := []int{10, 20, 30, 40}
	shareSlice := sourceSlice
	shareSlice[0] = 99
	fmt.Println("sourceSlice  :", sourceSlice)
	fmt.Println("shareSlice   :", shareSlice)
	fmt.Println("length       :", len(sourceSlice), " capacity:", cap(sourceSlice))
}
```

```
sourceSlice  : [99 20 30 40]
shareSlice   : [99 20 30 40]
length       : 4  capacity: 4
```

The same line — `shareSlice := sourceSlice` — does not produce a copy the way it does with an
array. When `shareSlice[0] = 99` is written, `sourceSlice` changes too, because both point to
the same backing array; what gets assigned is not the backing array itself, it is the small
header carrying the pointer to it. `length` and `capacity` come out equal here because the
slice literal was built with exactly four elements; when a slice is derived by slicing, the
two can diverge — that distinction is the Slice Operations lesson's subject.

The header being small and fixed in size has a direct consequence: the cost of copying a slice
is independent of how many elements the backing array holds. Assigning a ten-element slice and
assigning a ten-million-element slice both copy the same three fields; what gets copied is
never the elements themselves. The cost of assigning an array, by contrast, grows with its
length, because every element is copied one by one.

This distinction also determines where each type gets used. Small data whose length is known
ahead of the code and will never change — a four-byte IPv4 address, a fixed-length
checksum — is defined as an array; writing the size into the type signature is a way of
telling the compiler that size will never change. Everything whose length will change during
the run — the number of lines read, an accumulating result set — is kept as a slice, because a
slice's length is a field of the value, not of the type.

A slice not being comparable with `==` traces back to the same root. If a slice comparison
were to try to answer "is the content the same," the compiler would have to walk the backing
array from start to end on every comparison; instead, the language allows a slice to be
compared only with `nil`, not with another slice. The same `==` operator compiles for an array
and does not compile for a slice — because an array is the content it carries, while a slice
is a pointer leading to it.

**CT2.** — This distinction is run here with a compiler call on the source; the two printed
lines carry only the class "compiled" or "did not compile," the compiler's error text is not
read.

```go
// comparison.go — an array compares with ==, a slice cannot
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() {
	arrayEqual := `package trial
	func F() bool {
		a := [3]int{1, 2, 3}
		b := [3]int{1, 2, 3}
		return a == b
	}`
	sliceEqual := `package trial
	func F() bool {
		a := []int{1, 2, 3}
		b := []int{1, 2, 3}
		return a == b
	}`
	fmt.Println("two arrays compare with ==:", compiles(arrayEqual))
	fmt.Println("two slices compare with ==:", compiles(sliceEqual))
}
```

```
two arrays compare with ==: true
two slices compare with ==: false
```

#### The Same Rule Applies When Passing to a Function

Parameter passing is also an assignment, and the rule above applies here in exactly the same
way: an array parameter gets its own copy; a slice parameter shares the same header — and
therefore the same backing array. This is proof that Go makes no distinction at all between an
assignment between two variables and a function call: parameter binding follows the same rule
as the `=` sign.

```go
// passing.go — passing to a function is an assignment too
package main

import "fmt"

func changeArray(a [4]int) {
	a[0] = 999
}

func changeSliceElement(s []int) {
	s[0] = 999
}

func reassignSlice(s []int) {
	s = append(s, 777, 777, 777, 777, 777) // exceeds capacity, moves to a new backing array
}

func main() {
	array := [4]int{1, 2, 3, 4}
	changeArray(array)
	fmt.Println("array passed to function :", array)

	slice := []int{1, 2, 3, 4}
	changeSliceElement(slice)
	fmt.Println("slice element write      :", slice)

	slice2 := []int{1, 2, 3, 4}
	reassignSlice(slice2)
	fmt.Println("slice reassignment       :", slice2)
}
```

```
array passed to function : [1 2 3 4]
slice element write      : [999 2 3 4]
slice reassignment       : [1 2 3 4]
```

The first line is as expected: `changeArray` operates on its own copy; the caller's array does
not change. The second line confirms sharing: `changeSliceElement` writes to the backing array
its parameter points to, and this write is reflected back to the caller. The third line shows
a subtle point: when `reassignSlice` grows the parameter with `append` and **reassigns** it,
only its own local copy's header changes; the caller's `slice2` header still points to the old
backing array, and that change is lost when the function returns. Passing a slice to a
function shares the backing array but does not share the header — this is the function-call
counterpart of the header split in the Limiting Measure section.

The standard library's own `append` function returns a result for this very reason and expects
the caller to reassign it; even if the function grew the slice it received as a parameter and
kept it internally, this would not affect the caller's variable. The behavior of the
`reassignSlice` function in the third line is direct proof of why `append` is called in the
form `s = append(s, x)`: code that forgets to reassign the result silently stays with the old
slice once capacity is exceeded.

#### Limiting Measure: When a Slice Header Splits Away

What a slice shares is the backing array, not the header itself. The header is also a value
and is copied when assigned — its three fields (pointer, length, capacity) together. This
difference becomes visible the moment two variables stop pointing to the same backing array.

**CT3.** — The capacity here is not explicitly requested with `make`; `[]int{1, 2, 3}` is a
slice literal, and the compiler sets the capacity equal to three elements. The measurement
does not assume this capacity is fixed, it reads it from the run with `cap`.

```go
// header.go — when a slice header splits away from its copy
package main

import "fmt"

func main() {
	base := []int{1, 2, 3}
	share := base
	share[0] = 100
	fmt.Println("after element write   base:", base, " share:", share)

	previousCapacity := cap(base)
	base = append(base, 4)
	share[1] = 200
	fmt.Println("after append          base:", base, " share:", share)
	fmt.Println("capacity (this run) previous=", previousCapacity, " next=", cap(base))
}
```

```
after element write   base: [100 2 3]  share: [100 2 3]
after append          base: [100 2 3 4]  share: [100 200 3]
capacity (this run) previous= 3  next= 6
```

In the first line, `share[0] = 100` changes both `base` and `share` — both still point to the
same backing array, just as in the previous section. Then the call `base = append(base, 4)`
exceeds the capacity (there is no room for a fourth element in a three-element backing array);
in this case the language's runtime allocates a **new** backing array, copies the old data
into it, and updates `base`'s header to point to that new backing array. `share`'s header
stays unaware of this update — it still points to the old backing array. This is why
`share[1] = 200` changes only `share`; it leaves no trace on `base`.

The unpredictability of this transition carries the real risk. Someone looking at the source
code cannot tell, from the line itself alone, whether `share[1] = 200` will affect `base` —
the answer depends on how many times `share` and `base` crossed a shared `append` boundary
before that line is reached. When two slices share the same backing array and one is grown
while the other is not, the sharing between them ends without leaving any trace in the text.

**CT4.** — The two printed capacity numbers (3 and 6) are this run's observation, not the
language's promise. The Go specification guarantees that `append` will allocate a new backing
array when capacity is exceeded, but it does not specify **how much** the new capacity will
grow by; the growth rate is an implementation detail and can change between compiler versions.
Code should not read this line as a constant — it should read only the information "capacity
was exceeded, a new backing array arrived."

What gets copied here is now fully clear: `base` and `share` were never the same variable,
both had their own header from the start. The sharing came from the two headers pointing to
the **same backing array**; once `append` changes the backing array, the sharing ends. The
slice itself is a value — just as the array is a value — but the value it carries is not the
backing array's data, it is a pointer leading to it.

The practical counterpart of this is: if a function adds elements to the slice it receives
with `append` and wants that growth reflected back to the caller, it must **return** the grown
slice — just as `append`'s own signature does. Growing the slice variable that arrives as a
parameter, on its own, only updates that local variable; the caller's header is not affected
by this. The difference between array and slice thus comes down to a single sentence: an array
is a value and is copied entirely, a slice is also a value, but what it carries is a small
header leading to a shared backing array — neither is "passing by reference," both are passing
by value with different content. What this distinction has to do with pointers — when taking a
variable's address is required, when it is unnecessary — is measured separately in the
Pointers lesson; what is established here is only that a slice's own header is nothing more
than a unit of copying.

#### Summary

- An array's length is part of its type (`[4]int`); when an array is assigned, all elements
  are copied and the two variables become independent of each other.
- A slice is a three-field header: a pointer to the backing array, the length, the capacity.
  When a slice is assigned, this header is copied but the backing array is not; the two
  variables share the same backing array.
- A change written to the shared backing array is visible from both variables, because both
  point to the same memory.
- When an `append` call exceeds capacity, a new backing array is allocated, and only the
  variable holding the call's result moves to that new backing array; sharing is cut off here.
- Capacity's growth rate (such as from 3 to 6) is an implementation detail; the language's
  only promise is the statement "when capacity is exceeded, a new backing array is allocated."

#### Next Step

In this lesson, sharing was seen only when two variables directly carried the same slice —
through an assignment like `shareSlice := sourceSlice`, or by being passed as a parameter to a
function. But the same backing array is also shared when a slice is derived from another slice
by **slicing** — and in that case, when the sharing continues and when it cuts off depends not
on the written code, but on the derived slice's capacity. The next lesson measures this
dependency: the same `append` call, with the same source text, gives two different results at
two different capacities.

### Slice Operations

Source: https://academia.sh/en/courses/go-fundamentals/slice-operations

The same append call, with the same source text, gives two different results at two different capacities: sharing continues within the capacity limit and writing to the derived slice changes the source, while sharing is cut off once capacity is exceeded and the source stays untouched. Two notations close this gap: an independent copy taken with copy, and the three-index slice expression that explicitly bounds capacity.

The previous lesson closed with a question: the same `append` call, with the same source
text, gives two different results at two different capacities. The Arrays and Slices lesson
showed sharing only in direct assignment — in a line like `shareSlice := sourceSlice`. This
lesson looks at the more commonly encountered form of sharing: another slice derived from a
slice by **slicing**.

Slicing is one of the most frequently seen operations in Go source: walking a file's lines,
separating a network packet's header from its body, taking a particular section of a text all
use the same syntax. This prevalence also raises the cost of not knowing how sharing works —
because every slicing line establishes an invisible bond.

#### The Default for an Unwritten Slice Bound

A slicing expression has the form `source[start:end]`, and one or both of the two bounds may
go unwritten. This was exactly one of the fifteen unwritten cases the Zero Values lesson
measured: leaving slice bounds unwritten is **defined** — it corresponds to `0` when the start
is unwritten and to the slice's length when the end is unwritten.

```go
// bounds.go — the unwritten ends of a slicing bound
package main

import "fmt"

func main() {
	source := []int{10, 20, 30, 40}
	fmt.Println("source[:] :", source[:])
	fmt.Println("source[1:]:", source[1:])
	fmt.Println("source[:2]:", source[:2])
}
```

```
source[:] : [10 20 30 40]
source[1:]: [20 30 40]
source[:2]: [10 20]
```

All three are derived from the same `source` and all three point to the same backing array;
none of them takes a copy. `source[:]` leaves both bounds to their default and covers the
whole slice, `source[1:]` writes only the start, `source[:2]` writes only the end. The numbers
these three lines produce are the same as the Zero Values lesson's binding table; there, 8 of
the 15 unwritten cases fell into the **defined** class, and slice bounds were one of them.

An unwritten bound and a **wrongly written** bound are not the same thing. When the end was
left blank, it fell back to the slice's length; when the end is written explicitly and
exceeds capacity, the result is not a defined value, it is a run-time stop.

```go
// overrun.go — an unwritten bound is defined, a bound exceeding capacity is open
package main

import "fmt"

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

func main() {
	source := []int{10, 20, 30, 40}
	validPanicked := panics(func() { _ = source[1:3] })
	fmt.Println("source[1:3] (valid range)         panic=", validPanicked)
	overrunPanicked := panics(func() { _ = source[1:9] })
	fmt.Println("source[1:9] (end exceeds capacity) panic=", overrunPanicked)
}
```

```
source[1:3] (valid range)         panic= false
source[1:9] (end exceeds capacity) panic= true
```

A bound left blank and a bound written too large fall into two separate classes here: the
first is a defined value the language fills in itself, the second is an error the compiler
cannot catch and that is left to run time. The difference is not whether it is written, it is
whether the written value stays within capacity.

#### Writing to a Derived Slice Changes the Source

Slicing does not produce a new backing array; the derived slice points to the **same** backing
array the source points to, with a different length and starting point. This is why writing
to the derived slice also changes the source.

```go
// derived.go — writing to a slice derived by slicing changes the source
package main

import "fmt"

func main() {
	source := []int{10, 20, 30, 40}
	derived := source[1:3]
	derived[0] = 99
	fmt.Println("derived:", derived)
	fmt.Println("source :", source)
}
```

```
derived: [99 30]
source : [10 99 30 40]
```

`derived := source[1:3]` produces a two-element slice covering the second and third elements
(`20`, `30`). When `derived[0] = 99` is written, what changes is not `derived`'s own data, it
is the second element of the backing array shared with `source`; this is why `source[1]` also
becomes `99`. This is the same sharing seen in direct assignment in the Arrays and Slices
lesson — only how the slice was obtained changed, the rule itself did not.

This sharing also continues among more than two slices, because all of them stay bound to the
same backing array. When two slices are derived from the source so that they **overlap**, a
write to the overlapping position is visible from both.

```go
// overlapping.go — two slices overlapping from a source, a write to the shared position shows in both
package main

import "fmt"

func main() {
	source := []int{10, 20, 30, 40, 50}
	left := source[0:3]
	right := source[2:5]
	left[2] = 999 // left's last element, the source's third element, is right's first element
	fmt.Println("left  :", left)
	fmt.Println("right :", right)
	fmt.Println("source:", source)
}
```

```
left  : [10 20 999]
right : [999 40 50]
source: [10 20 999 40 50]
```

`left` covers the first three elements, `right` covers the last three; the two overlap at the
third position (`source[2]`). When `left[2] = 999` is written, the one memory position that
changes is exactly the one all three — `left`, `right` and `source` — share; this is why the
change is visible in all three. It does not matter how many variables point to the same
backing array, what matters is only which positions overlap.

#### The Same Call, Two Capacities, Two Results

Sharing up to this point was predictable: as long as a slice existed, every slice derived from
it shared the same backing array with it. `append` breaks this predictability, because it does
two different things depending on whether it exceeds capacity: if it does not, it writes to
the existing backing array and sharing continues; if it does, it allocates a new backing array
and sharing is cut off. The source text can be exactly the same in both.

**CT5.** — Both slices are filled with the same three elements, in the same order; the only
variable that creates the difference is capacity. Capacity is given explicitly as the second
argument to `make` so that the difference is not left to run-to-run chance.

```go
// growth.go — the same append call, two capacities, two results
package main

import "fmt"

func add(s []int, value int) []int {
	return append(s, value)
}

func main() {
	roomy := make([]int, 3, 5) // two extra slots in capacity
	copy(roomy, []int{1, 2, 3})
	full := make([]int, 3, 3) // no extra room in capacity
	copy(full, []int{1, 2, 3})

	roomy2 := add(roomy, 4)
	roomy2[0] = 100
	fmt.Println("roomy (cap=5) after roomy:", roomy, " roomy2:", roomy2)

	full2 := add(full, 4)
	full2[0] = 100
	fmt.Println("full  (cap=3) after full:", full, " full2:", full2)
}
```

```
roomy (cap=5) after roomy: [100 2 3]  roomy2: [100 2 3 4]
full  (cap=3) after full: [1 2 3]  full2: [100 2 3 4]
```

The `add` function was called on both with the **same** source text: `append(s, value)`.
`roomy` carried two extra slots in its capacity; `append` used that room, did not grow the
backing array, and the `roomy2` it returned still points to the same backing array. This is
why `roomy` also changed when `roomy2[0] = 100` was written — sharing continued. `full`, on
the other hand, was at full capacity; `append` allocated a new backing array, and `full2`
pointed to that new backing array. `full2[0] = 100` this time changed only `full2`, `full`
stayed untouched.

This is the course's clearest line falling into the **open** class: the language guarantees
that `append` will allocate a new backing array when capacity is exceeded, but whether the
result will be shared with the source cannot be read from the source code. The same line —
`append(s, value)` — continues sharing in one call, cuts it off in another; a reader cannot
say which it will be without knowing the capacity.

The place where this uncertainty most often causes confusion in practice is functions that
grow a slice. If a function adds elements to the slice it receives as a parameter with
`append` and does not return the result, the caller's variable is **never** updated,
regardless of capacity — even when capacity would have been enough.

**CT6.** — `buffer`'s capacity here is deliberately kept ample (room for ten elements, three
elements will be added); the goal is to remove capacity shortage from the list of possible
explanations. Had capacity been kept tight, why the result did not change could be attributed
to two causes at once, and the measurement would become ambiguous.

```go
// accumulate.go — append that does not return its result never updates the caller
package main

import "fmt"

func appendUnsafely(target []int, source []int) {
	for _, v := range source {
		target = append(target, v)
	}
	fmt.Println("target inside function:", target)
}

func main() {
	buffer := make([]int, 0, 10) // an empty accumulator with ample capacity
	appendUnsafely(buffer, []int{1, 2, 3})
	fmt.Println("buffer at the caller  :", buffer)
}
```

```
target inside function: [1 2 3]
buffer at the caller  : []
```

`buffer`'s capacity was ten, more than enough for three elements — sharing could have
continued as in the previous measurement's `roomy` example. But `appendUnsafely` assigns every
`append` result to its own local `target` variable and returns nothing; when the function
returns, `buffer`'s header has not changed at all, `len(buffer)` is still zero. Even capacity
being sufficient does not rescue sharing in this case, because the problem is not in the
backing array, it is that the updated header is never carried back to the caller.

#### copy and the Three-Index Slice Expression: Two Notations That Close the Gap

This unpredictability can be closed with two notations. The first is the `copy` built-in
function: it takes the target and the source as two slices with separate backing arrays,
copies the elements one by one, and returns the number of elements copied.

```go
// copying.go — an independent copy with copy, a bounded capacity with the three-index slice
package main

import "fmt"

func add(s []int, value int) []int {
	return append(s, value)
}

func main() {
	source := []int{10, 20, 30, 40}

	independent := make([]int, len(source))
	count := copy(independent, source)
	independent[0] = 7777
	fmt.Println("copy count:", count, " independent:", independent, " source:", source)

	bounded := source[1:3:3]
	fmt.Println("bounded length=", len(bounded), " capacity=", cap(bounded))
	bounded2 := add(bounded, 999)
	bounded2[0] = 8888
	fmt.Println("bounded2:", bounded2, " source:", source)
}
```

```
copy count: 4  independent: [7777 20 30 40]  source: [10 20 30 40]
bounded length= 2  capacity= 2
bounded2: [8888 30 999]  source: [10 20 30 40]
```

After the call `copy(independent, source)`, `independent` has its own backing array; changing
its first element does not affect `source` at all. **CT7.** — The returned `count` value is
not assumed anywhere, it is printed directly; `copy` copies the **smaller** of the target's or
source's length, and since both are four in this example, it copies four.

`copy`'s target and source do not have to be in two independent backing arrays; it works
correctly even if the two point to the **same** backing array. This is useful for shifting
elements within a single slice.

```go
// shifting.go — copy also works correctly on overlapping ranges within the same backing array
package main

import "fmt"

func main() {
	slice := []int{1, 2, 3, 4, 5}
	shifted := copy(slice[0:], slice[1:4]) // shift the second to fourth elements one position left
	fmt.Println("shifted count:", shifted, " slice:", slice)
}
```

```
shifted count: 3  slice: [2 3 4 4 5]
```

`slice[1:4]` and `slice[0:]` are two overlapping slices of the same backing array; `copy`
shifts the second through fourth elements one position to the left, and the last element
(`5`) stays in place, because the shift covered only the three elements copied. This
behavior is not chance, it is part of `copy`'s own promise: even when source and target
overlap, the result is one where the elements are copied **correctly**, not merely in
order. A loop written by hand, advancing index by index to do the same operation, could
write over elements early depending on the shift direction and corrupt the data; `copy`
does not carry this risk.

The second notation is the three-index slice expression: `source[1:3:3]` specifies not only
the start and end, but also the **capacity** with a third value. `bounded`'s capacity here is
set equal to its length — no extra room is left. This is why the `append` call on `bounded`
**certainly** exceeds capacity and allocates a new backing array; when `bounded2[0] = 8888` is
written, `source` stays untouched. In the earlier section's `full` example, capacity being
exact was a coincidence (the three-element literal was built with exactly three capacity); the
three-index slice expression **removes this from chance** and turns it into a notational
decision — it locks capacity to the desired limit.

**CT8.** — The two closing notations serve two separate purposes: `copy` is used when a
completely separate copy of the data is wanted, while the three-index slice expression is used
when sharing the same backing array with the source is meant to continue, and only wants to
**guarantee** that a later `append` will cut that sharing off. The first ends sharing from the
start, the second decides in advance that growth will cut sharing off.

What these two notations have in common is that neither leaves capacity to the code reader's
guess. `copy` removes capacity from the subject entirely — whatever the target's own capacity
is, that is what gets used, with no relationship left to the source's capacity. The
three-index slice expression, by contrast, does not zero out capacity, it **narrows** it:
normally the notation `source[1:3]` would inherit all the remaining capacity (up to the end of
the source), and the third index cuts off that inheritance, confining the slice to its own
size. Both arrive at the same outcome — making sure a later `append` will not touch the
source — but with different costs: `copy` requires a copying operation, the three-index slice
expression moves no data at all, it only bounds a number.

What the three sections measure comes down to a single sentence: slicing always shares;
`append` shares or does not, depending on capacity; `copy` and the three-index slice
expression turn sharing into a notational-level decision. Looking at the source text, the
answer to "does this line share" is known in advance in the third case; in the first two it is
not known, it is learned only by running.

#### Summary

- A slicing expression's unwritten bounds are defined: `0` is used when the start is
  unwritten, the slice's length when the end is unwritten; this is an example that falls into
  the defined class of the Zero Values lesson's binding 8/4/3 distribution.
- A slice derived by slicing points to the same backing array as the source; writing to the
  derived slice also changes the source.
- As long as `append` does not exceed capacity, it writes to the existing backing array and
  sharing continues; once it exceeds capacity, it allocates a new backing array and sharing is
  cut off. The same source text can give two different results at two different capacities.
- `copy` copies elements one by one into a backing array independently owned by the target
  and returns the number of elements copied; it leaves no sharing with the source.
- The three-index slice expression (`source[start:end:capacity]`) explicitly bounds capacity
  and guarantees, at the notational level, that a later `append` will separate from the
  source.

#### Next Step

The sharing measured in these two lessons was among ordered structures like arrays and
slices; the next topic looks at key-value matching. A map is also a header and shares an
underlying structure, but the question it asks is different: what happens when a key that
does not exist is accessed, and does that answer arrive as a single value or two values? The
next lesson separates these two — and also why iteration order is never printed directly.

### Maps

Source: https://academia.sh/en/courses/go-fundamentals/maps

Reading from a map falls into four separate classes in four separate forms: an existing key gives its value, a missing key gives an absence answer along with the zero value, reading from a nil map is defined but writing to it panics, a deleted key behaves as though it never existed again. Iteration order is deliberately random and is never printed directly anywhere. The limiting measure shows why comma-ok access is necessary: a record whose value is zero and a record that never existed cannot be told apart with single-value access.

The previous lesson measured that whether sharing in slices continues or is cut off is read
from capacity, not from the source code, and left a second question to be separated out: what
happens when a key that does not exist is accessed in key-value matching, and does that answer
arrive as a single value, or two? This lesson starts with that question.

The name for key-value matching in Go is **map**. Like a slice, a map is also a header — it
carries not its own data, but a pointer leading to a hash table the runtime manages. But the
answer a map gives to an unwritten key is more layered than the answer a slice gives to an
unwritten bound: four separate access forms fall into four separate classes.

The source of this layering is that a slice is accessed only by **position**, while a map is
accessed by **key**. Accessing an invalid position in a slice always does the same thing — an
index that overruns the bounds panics. In a map, "key not present" is not a panic, because a
map assumes that looking up a key that does not exist is a normal outcome, an ordinary query
rather than an exhausted bound. A panic only comes when the map itself never existed at all
(on a write attempt).

#### Four Access Forms

```go
// maps.go — four access forms on a map, four separate classes
package main

import "fmt"

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

func main() {
	counter := map[string]int{"pen": 3, "notebook": 7, "eraser": 1}

	value, exists := counter["pen"]
	fmt.Println("existing key        : value=", value, " exists=", exists)

	value, exists = counter["manager"]
	fmt.Println("missing key         : value=", value, " exists=", exists)

	var emptyCounter map[string]int
	fmt.Println("reading a nil map   : value=", emptyCounter["anything"])

	writePanicked := panics(func() { emptyCounter["new"] = 1 })
	fmt.Println("writing to a nil map: panic=", writePanicked)

	delete(counter, "eraser")
	deletedValue, deletedExists := counter["eraser"]
	fmt.Println("deleted key         : value=", deletedValue, " exists=", deletedExists)
}
```

```
existing key        : value= 3  exists= true
missing key         : value= 0  exists= false
reading a nil map   : value= 0
writing to a nil map: panic= true
deleted key         : value= 0  exists= false
```

Five lines separate four classes. The notation `value, exists := counter["pen"]` in the
source is named **comma-ok access**: reading from a map, if wanted, returns the value together
with existence information instead of a single value. Reading from an existing key works as
expected. A missing key (`"manager"`, which never appears in `counter`) gives no error, it
gives an absence answer along with `int`'s zero value — this second answer is a separate
section's subject below. Reading from a map that was never given an initial value at all —
`emptyCounter`, in the `nil` case — also works and again returns the zero value; in the Zero
Values lesson's binding 8/4/3 distribution, this line was one of the eight cases falling into
the **defined** class. But **writing** to that same `nil` map falls into a different class:
the language does not place a zero value here; the runtime panics — one of the same
distribution's three cases left **open**. The last line shows that a deleted key cannot be
told apart, in behavior, from a key that never existed at all: after `delete`,
`counter["eraser"]` gives the zero value and `false`, exactly like `counter["manager"]`.

Three of the four access forms fit the same mold — reading is always safe, writing is not
safe on a `nil` map. This asymmetry is not arbitrary: reading from a map only searches the
hash table and returns the zero value if it does not find one, but writing requires adding an
entry to the hash table, and a `nil` map does not yet have **any** hash table at all — the
place to add to is not a zero-length backing array the way it is in a slice's `nil` case, it
is entirely **absent**. This asymmetry extends to `delete` as well: trying to delete a key
that does not exist from a `nil` map also does not panic, because `delete` behaves like
reading, not like writing — if it finds nothing to delete, it silently does nothing.

#### A Map Key Type Must Be Comparable

The Arrays and Slices lesson measured that an array can be compared with `==` and a slice
cannot, and left this connected to usability as a map key in a single sentence. The map's own
lookup mechanism makes this connection mandatory: to find a key, a map compares it against
existing keys through **equality**, and this is why the key type must be comparable.

**CT9.** — This constraint is run here with a compiler call; only the class "compiled" or
"did not compile" is printed.

```go
// key.go — a map key type must be comparable
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() {
	arrayKey := `package trial
	func F() {
		m := map[[2]int]string{}
		m[[2]int{0, 0}] = "top-left"
	}`
	sliceKey := `package trial
	func F() {
		m := map[[]int]string{}
		_ = m
	}`
	fmt.Println("array-keyed map :", compiles(arrayKey))
	fmt.Println("slice-keyed map :", compiles(sliceKey))
}
```

```
array-keyed map : true
slice-keyed map : false
```

The array-keyed map compiles, because the array's comparability was established in the
previous lesson. The slice-keyed map does not compile — because a slice is not comparable,
even the map itself cannot be **defined**, before a single entry is added. This shows how the
same property (comparability) of two composite types produces results in two separate places:
one in the `==` operator, the other in whether a type is accepted as a map's key type.

#### Passing to a Function: A Map Behaves Differently From a Slice

The previous lesson measured that a function adding elements to the slice it receives with
`append` never reaches the caller unless the result is returned. A map has no such trap.

```go
// passing.go — writing to a map reaches the caller even without returning the result
package main

import "fmt"

func addSafely(m map[string]int, key string, value int) {
	m[key] = value
}

func main() {
	counter := map[string]int{"pen": 3}
	addSafely(counter, "eraser", 5)
	fmt.Println("map at the caller:", counter["eraser"])
}
```

```
map at the caller: 5
```

`addSafely` returns nothing, but `counter["eraser"]` is `5` when looked at from the caller.
The difference comes from a slice's `append` and a map's write operation doing different
things: when `append` exceeds capacity, it allocates a **new** backing array and updates the
header — this update happens only in the local copy. Writing to a map, by contrast, changes
the existing hash table **in place**; the header itself never changes, only the table's
content changes. Because the map variable passed as a parameter points to the same table as
the one at the caller, every change made to the table is visible to the caller. This is
exactly where a map departs from a slice: a slice sometimes has to change its backing array as
it grows, while a map never changes its own table as it grows — it only expands it.

#### Capacity Is a Hint, Not a Capacity Limit

The second argument that can be given when building a map with `make` does not carry the same
promise as capacity in a slice. In a slice, capacity was a limit — once exceeded, `append`
would allocate a new backing array. In a map, that same number is only a **hint**; it sets the
hash table's starting size but does not limit the map's growth in any way.

```go
// hint.go — a map's capacity is a hint, not a limit
package main

import "fmt"

func main() {
	small := make(map[string]int, 2) // only a starting hint
	for i, name := range []string{"a", "b", "c", "d", "e", "f"} {
		small[name] = i
	}
	fmt.Println("initial hint=2, keys added:", len(small))
}
```

```
initial hint=2, keys added: 6
```

`small` was built with a hint allocated for only two keys, but six keys were added without a
problem. Adding a third element to a slice built with the notation `make([]int, 0, 2)` would
force `append` to allocate a new backing array, and this would lead to the sharing cut-off
measured in the Slice Operations lesson; a map has no such threshold to cross, because the
second argument does not **commit** to a capacity, it only gives the runtime an estimate. A
map's own internal table grows when it needs to, but this growth does not produce a header
change visible to the caller the way a slice's `append` does — because a map's header is
already the single header shared with the caller, it has no second copy to be reassigned.

#### Iteration Order: A Defined Uncertainty

The order of iterating over a map with `range` is deliberately random; the same map, even
within the same program, can be walked in a different order across two separate `range`
calls. This is not an implementation flaw, it is a **defined uncertainty**: the language
deliberately shuffles the order so that iteration order never becomes something any code
depends on. This is why no lesson in the course prints a map directly; keys are sorted
instead.

```go
// iteration.go — a map is never printed directly, its keys are sorted first
package main

import (
	"fmt"
	"sort"
)

func main() {
	counter := map[string]int{"pen": 3, "notebook": 7, "eraser": 1}
	keys := make([]string, 0, len(counter))
	for k := range counter {
		keys = append(keys, k)
	}
	sort.Strings(keys)
	for _, k := range keys {
		fmt.Println(" ", k, "=", counter[k])
	}
}
```

```
  eraser = 1
  notebook = 7
  pen = 3
```

The order printed here is alphabetical, because what produces it is the `sort.Strings`
call — not the map's own iteration order. The loop `for k := range counter` only gathers the
keys into a slice; the order in which it gathers them is irrelevant to this measurement,
because `sort.Strings` brings the result to the same order on every run.

The purpose of deliberately keeping the order random is to prevent code from **becoming
dependent on order** from the start. Had the order been consistent — for instance, always
following insertion order — code relying on that consistency would accumulate over time: code
that formats a loop's output according to insertion order, or that walks two maps in the same
order to compare them. Such code would break silently the moment the hash table's internal
structure changed (for instance, when a key is deleted and re-added). Keeping the order random
from the start guarantees that this dependency never forms — a programmer cannot rely on
order, because there is no fixed order to rely on.

Printing a map directly with `fmt.Println(counter)` also works technically, and the
formatting package can even produce output sorted by key — but this is the formatting
package's own choice, not the language's guarantee; the same guarantee does not exist when
iterating directly with `range`. The measurement does not rely on this difference and does its
own sorting by hand; no lesson in the course prints a map by relying on `range`'s output.

**CT10.** — Sorting is done here on a small set of three keys; the measurement's goal is not
to show sorting itself, it is to show that the map's **own** order is never relied on at all.
The same result would also be obtained with hundreds of keys, only the output would be longer.

#### Limiting Measure: A Zero Value and Absence Cannot Be Told Apart With a Single Value

Why comma-ok access exists is not fully understood without seeing where single-value access
falls short. When a key's value is **the same** as the type's zero value, a map can no longer
tell two different situations apart: the key exists and its value is zero, or the key does not
exist at all.

```go
// ambiguity.go — a zero-valued record and absence cannot be told apart with a single value
package main

import "fmt"

func main() {
	counter := map[string]int{"pen": 3}
	counter["new-product"] = 0 // record exists, value is deliberately zero
	singleValue := counter["new-product"]
	singleValueMissing := counter["never-added"]
	fmt.Println("present, value is zero  : single value=", singleValue)
	fmt.Println("never added             : single value=", singleValueMissing)

	_, exists1 := counter["new-product"]
	_, exists2 := counter["never-added"]
	fmt.Println("two-value distinction: new-product exists=", exists1, " never-added exists=", exists2)
}
```

```
present, value is zero  : single value= 0
never added             : single value= 0
two-value distinction: new-product exists= true  never-added exists= false
```

The first two lines are exactly the same: `0`. Looked at from single-value access, there is no
difference at all between `"new-product"` and `"never-added"` — both give zero. The third line
removes this blindness: `exists1` is `true`, `exists2` is `false`. The difference is not in the
value itself, it is in whether the map holds that key **at all**.

This limitation produces real consequences in practice. If a counter map distinguishes "never
seen" from "seen but incremented zero times," or a configuration map distinguishes "took the
default" from "explicitly set to zero," single-value access can never see this distinction at
all. Comma-ok access here is not merely a required notation, it becomes the **only** correct
notation — being "defined," on its own, does not mean being "sufficient."

This limitation requires rereading the lesson's opening four classes as well. The two lines
in the "missing key" and "deleted key" sections gave the same `0` when looked at with a single
value; before comma-ok access was added, it was not visible that these two — and a third key
whose value is genuinely zero — could not be told apart from each other. The single answer a
map gives does not carry the difference between "a value exists" and "no value exists, zero
was put in its place"; what carries it is the second return value.

#### Summary

- Four access forms on a map fall into four separate classes: an existing key gives its
  value, a missing key gives an absence answer with the zero value, reading from a `nil` map
  is defined but writing to it panics, a deleted key behaves as though it never existed again.
- Writing to a map changes the existing table in place; a write made to a map passed to a
  function reaches the caller even without returning the result — different from a slice's
  `append` behavior.
- A map's iteration order is deliberately random and is never printed directly in any lesson;
  keys are sorted with `sort.Strings` and printed that way.
- Comma-ok access is the only way to tell a zero-valued record apart from absence; single-value
  access returns both with the same zero value.

#### Next Step

A map's key was an indivisible value like a single integer or string; it carried a single
piece of information, as in the counter example. The next type holds several fields together,
and each of its own fields can be accessed separately — where a map is accessed with a single
key, this new type is accessed with several named fields. A struct also starts with something
unwritten: what happens if its fields are not given a value? But unlike a map, the fields'
**order** also carries meaning, and relying on that order has its own fragility — where a map
is accessed by key name, this new type carries a field where accessing by position is also an
option.

### Structs

Source: https://academia.sh/en/courses/go-fundamentals/structs

The zero value of unwritten fields, the fragility carried by fields being writable positionally, and embedding carrying unwritten names to the outer type are measured: an embedded type's methods can be called without ever being written on the outer type. The limiting measure shows that embedding is not inheritance: when an embedded type's method is called, the receiver is the embedded value itself, not the outer struct.

A map's key was an indivisible value: a single string or integer, carrying a single piece of
information. A struct is a composite type that holds several fields together, with each field
accessed by its name. It shares one question with a map — what happens if fields are not
given a value — but a struct has two more questions of its own: does the fields' **order**
carry meaning, and what happens when a struct is **embedded** inside another struct?

These two questions sit on an axis separate from the "copy or sharing" question established
in the array and slice lessons. The Arrays and Slices lesson measured how much of a value gets
copied; a struct is also a value and follows the same rule. But a struct's own question is not
about copying, it is about the **naming** of fields: a field's name, its position, and its
source — whether it was written — carry three separate pieces of information, independent of
each other.

#### The Zero Value of Unwritten Fields

When a struct variable is declared without an initial value, each of its fields takes its own
type's zero value — a string field gets an empty string, an integer field gets zero, a boolean
field gets `false`. This is one of the cases falling into the **defined** class of the Zero
Values lesson's binding 8/4/3 distribution; it is mentioned here in a single sentence, the
whole distribution is not rebuilt.

```go
// zero.go — the zero value of unwritten struct fields
package main

import "fmt"

type Record struct {
	Name   string
	Number int
	Active bool
}

func main() {
	var empty Record
	fmt.Printf("empty record: %q %d %t\n", empty.Name, empty.Number, empty.Active)
}
```

```
empty record: "" 0 false
```

The three fields carry the zero value of three separate types, and all three come from a
single line, the notation `var empty Record`. No field was given a value explicitly; because
the struct itself knows which field is which type, it fills in each one's zero value itself.
This means a struct variable does not need a separate constructor call to be built —
`var empty Record` on its own produces a usable value, leaving no field in a "not yet built"
state.

This filling-in also works for embedded types: if a struct embeds another struct, the embedded
field itself also takes its own zero value.

```go
// embedded-zero.go — an embedded type's zero value is also filled in automatically
package main

import "fmt"

type Base struct {
	Name string
}

type Extended struct {
	Base
	Password string
}

func main() {
	var g Extended
	fmt.Printf("empty extended: Name=%q Password=%q\n", g.Name, g.Password)
}
```

```
empty extended: Name="" Password=""
```

`g.Name` here is `Base`'s field, `g.Password` is `Extended`'s own field; both came from a
single line, `var g Extended`. Zero-value filling holds for nested structs too, because the
rule is the same at every level: every field takes its own type's zero value, even when the
field itself is another struct.

A struct is also a value, just like an array, and is copied entirely when assigned — every
struct example seen throughout this lesson stands under this rule. After `copy := source` is
written, a change made to `copy` does not affect `source` at all, because the two variables
now carry two independent copies. This behavior will gain its real importance in the Pointers
lesson: a function that wants to change a struct cannot change anything at the caller when it
is given a copy.

#### The Fragility of Fields Being Writable Positionally

A struct literal can be written in two forms: without writing field names, purely by **order**
(positional), or by matching each value to its own field name (keyed). Positional notation is
short, but it rests on an assumption — the assumption that the field arrangement at the time
it was written will stay the same as the struct's **current** definition. When this assumption
breaks, the compiler gives no warning at all, as long as the two fields are the same type.

```go
// ordering.go — positional notation depends on field order, keyed notation does not
package main

import "fmt"

type AddressV1 struct {
	City    string
	Country string
}

type AddressV2 struct { // field order changed: Country moved to the front
	Country string
	City    string
}

func main() {
	v1 := AddressV1{"Ankara", "Turkey"}
	v2 := AddressV2{"Ankara", "Turkey"} // code was not updated, values written for V1
	fmt.Println("V1 positional:", "City=", v1.City, " Country=", v1.Country)
	fmt.Println("V2 positional:", "Country=", v2.Country, " City=", v2.City)
	v2Correct := AddressV2{City: "Ankara", Country: "Turkey"}
	fmt.Println("V2 keyed     :", "Country=", v2Correct.Country, " City=", v2Correct.City)
}
```

```
V1 positional: City= Ankara  Country= Turkey
V2 positional: Country= Ankara  City= Turkey
V2 keyed     : Country= Turkey  City= Ankara
```

`AddressV2` carries the same two fields as `AddressV1`, only its order has changed: `Country`
has been moved to the front. The line `AddressV2{"Ankara", "Turkey"}` was left **completely
unupdated** — it still assumes the old order (city first, then country). The compiler rejects
nothing here, because both fields are of type `string`; positional notation only looks at
position, it never asks for the name. The result: `Ankara` is now written into the `Country`
field, `Turkey` into the `City` field — the values have swapped places. Keyed notation puts
the same two values into the correct field no matter what order they are written in;
`v2Correct` is entirely independent of field order.

This fragility becomes visible only when field order **changes**, and the compiler never
catches it as long as the two fields are the same type — none of the four rejected cases
(unused variable, unused import, an unwritten return, implicit type conversion) covers this
situation. Had the fields' types been **different**, positional notation would not compile
and the error would appear at compile time; the type staying the same postpones the error to
run time, or even to never being noticed at all.

**CT11.** — `AddressV1` and `AddressV2` are deliberately built here with two fields, both of
the same type; the goal is to show the narrowest case the compiler cannot catch. Had a third
field of a different type been added, positional notation would not compile on that field, and
the fragility would not be visible.

This situation arises in a real codebase when a struct's field order is changed **later**:
the struct's definition is updated, but every line that builds it with positional notation is
not updated automatically. Because the compiler gives no warning, this becomes a defect class
that is hard to notice without running the code and checking the result by eye — keyed
notation removes this class entirely, because which value goes into which field stands in the
notation itself, entirely independent of field order.

#### Comparability Is Inherited From the Fields

The Arrays and Slices lesson measured that an array can be compared with `==` and a slice
cannot, and left it in a single sentence that this would carry over to structs too. Whether a
struct is comparable is not a rule of its own, it is the sum of its **fields'** comparability.

**CT12.** — This measurement is run here with a compiler call, and only the class "compiled"
or "did not compile" is printed; both type definitions carry a single field that looks the
same, and only that field's type determines the difference.

```go
// comparison.go — a struct's comparability is inherited from its fields
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() {
	arrayField := `package trial
	type K struct { Labels [2]string }
	func F() bool {
		a := K{[2]string{"x", "y"}}
		b := K{[2]string{"x", "y"}}
		return a == b
	}`
	sliceField := `package trial
	type K struct { Labels []string }
	func F() bool {
		a := K{[]string{"x", "y"}}
		b := K{[]string{"x", "y"}}
		return a == b
	}`
	fmt.Println("array-field-only struct:", compiles(arrayField))
	fmt.Println("slice-field struct     :", compiles(sliceField))
}
```

```
array-field-only struct: true
slice-field struct     : false
```

Both structs carry a single field; both appear to hold the same information (two labels). The
difference is in the field's type: one is an array, the other a slice. The array-field struct
can be compared with `==` because the array itself is comparable; the slice-field struct
cannot be compared because a slice is not comparable, and this property passes to the struct
**exactly as is**. A struct does not define a comparison rule of its own — it inherits its
fields' rule.

#### Embedding: Unwritten Names Carried to the Outer Type

A struct can contain, instead of a named field, a field that carries only another type's
name; this is called **embedding**. The embedded type's fields and methods become directly
callable through the outer type, without ever being written on the outer type. This lets
several behaviors be carried to the outer type at once with a single field; in the measurement
below, `Base`'s two methods appear on the `Extended` side without ever being written there.

```go
// embedding.go — an embedded type's names are carried to the outer type without being written
package main

import "fmt"

type Base struct {
	Name string
}

func (t Base) Greet() string {
	return "Hello, " + t.Name
}

func (t Base) WhoIsSpeaking() string {
	return fmt.Sprintf("%T says: %s", t, t.Name)
}

type Extended struct {
	Base
	Password string
}

func (g Extended) Verify(s string) bool {
	return g.Password == s
}

func main() {
	g := Extended{Base: Base{Name: "Alice"}, Password: "secret"}
	fmt.Println("embedded field direct  :", g.Name)
	fmt.Println("embedded method direct :", g.Greet())
	fmt.Println("own method             :", g.Verify("secret"))
}
```

```
embedded field direct  : Alice
embedded method direct : Hello, Alice
own method             : true
```

In the `Extended` struct there is no field written as `Name`, no method written as `Greet`
either — both belong to `Base`. But `g.Name` and `g.Greet()` work, as if `Extended` had
defined both itself. `Base`'s two methods (`Greet`, `WhoIsSpeaking`) were carried to `Extended`
**without being written**; the one method `Extended` writes for itself is `Verify`. An
embedded type's names appear as though they were the embedding type's own names — unlike a
key in a map, access here happens not by name, but directly by **field selection**.

**CT13.** — The number of methods carried over is two here (`Greet`, `WhoIsSpeaking`); the
number depends on how many methods were written on `Base`, and is independent of how many
methods the embedding type writes itself. Even if `Extended` wrote zero methods, both would
still be visible.

Embedding also works with a struct embedding **more than one** type, and the carried names
stack up; if two embedded types have a field or method with the same name, this lesson does
not test that collision, but the compiler rejects direct access from the outer type in such a
collision — which embedded type is meant has to be written explicitly. This lesson measures
only a **single** level of embedding; one embedded type whose name and methods do not collide.

#### Limiting Measure: Embedding Is Not Inheritance

Embedding resembles inheritance at the level of call notation, but it is not the same thing.
The difference lies in **who the receiver is** inside the called method.

```go
// receiver.go — when an embedded method is called, the receiver is the embedded value itself
package main

import "fmt"

type Base struct {
	Name string
}

func (t Base) WhoIsSpeaking() string {
	return fmt.Sprintf("%T says: %s", t, t.Name)
}

type Extended struct {
	Base
	Password string
}

func main() {
	g := Extended{Base: Base{Name: "Alice"}, Password: "secret"}
	fmt.Println("method naming the receiver:", g.WhoIsSpeaking())
}
```

```
method naming the receiver: main.Base says: Alice
```

The call `g.WhoIsSpeaking()` was made through `Extended`, but the type printed inside the
method with `%T` is `main.Base` — not `main.Extended`. This proves that the receiver is not
`g` itself, it is the embedded `Base` value inside `g`. In languages with inheritance, a
superclass method called through a subclass can see the **full identity** of the object
calling it; in Go, an embedded method can never step outside its own type — none of `Base`'s
methods know that `Extended` exists, because at the moment `Base` was defined, a type called
`Extended` did not exist yet. Embedding is the compiler **automatically expanding** the
notation `g.WhoIsSpeaking()` into `g.Base.WhoIsSpeaking()`; it is not a new call form, it is a
shortened notation for an existing call.

This becomes even more evident when the outer type defines a field with the **same name** as
the embedded type.

```go
// shadow.go — the outer type's same-named field shadows the embedded one, but the method is unaffected
package main

import "fmt"

type Base struct {
	Name string
}

func (t Base) Greet() string {
	return "Base says: Hello " + t.Name
}

type Extended struct {
	Base
	Name string // same-named field on the outer type, shadows the embedded one
}

func main() {
	g := Extended{Base: Base{Name: "EmbeddedName"}, Name: "OuterName"}
	fmt.Println("g.Name (direct)       :", g.Name)
	fmt.Println("g.Base.Name (explicit):", g.Base.Name)
	fmt.Println("g.Greet() (embedded)  :", g.Greet())
}
```

```
g.Name (direct)       : OuterName
g.Base.Name (explicit): EmbeddedName
g.Greet() (embedded)  : Base says: Hello EmbeddedName
```

When `Extended` defines its own `Name` field, that field **shadows** the embedded `Base.Name`:
`g.Name` now goes to the outer type's own field (`"OuterName"`); reaching the embedded field
requires writing `g.Base.Name` explicitly. But the call `g.Greet()` still uses
`"EmbeddedName"` — because `Greet` belongs to `Base`, and `Base`'s own `Name` was never
shadowed at all, only its **visibility** from the outer type changed. This is further proof
that the receiver is the embedded value itself: the outer type reusing the same name changes
nothing inside the embedded type itself.

#### Summary

- An unwritten struct field takes its own type's zero value; this is an example that falls
  into the defined class of the Zero Values lesson's binding distribution.
- Positional struct literals depend on field order; if two fields are the same type, the
  compiler catches nothing when the order changes, and values silently swap places. Keyed
  notation does not carry this fragility.
- A struct's comparability with `==` is not a rule of its own; it is the sum of every field's
  comparability — a single slice field makes the struct non-comparable.
- Embedding carries the embedded type's fields and methods to the outer type without being
  written; access happens directly by field selection.
- When an embedded method is called, the receiver is the embedded value itself, not the outer
  struct; embedding is not inheritance, it is a shorthand the compiler expands automatically.

#### Next Step

In this lesson, the notation `Base{Name: "Alice"}` was a value, and access to `g.Name`
happened directly through that value. But a method that wants to change a struct, if it
operates on its own copy, never affects the caller — just like an array being copied when
passed to a function. The next lesson looks at this problem: why is it not possible to change
a struct without taking a variable's **address**, and once the address is taken, which calls
affect the caller?

### Pointers

Source: https://academia.sh/en/courses/go-fundamentals/pointers

Reading from a nil pointer is left open: the language places nothing, the compiler does not reject it either, the result is a panic. Only two of four calls change the caller's value — a value written without taking an address, whether in a function parameter or a method receiver, only operates on its own copy. The limiting measure shows that a pointer is also a value: reassigning a pointer changes only the local copy, while writing to what it points to changes the caller.

The Structs lesson closed with a struct being copied entirely when assigned, and a function
that operates on the copy passed to it being unable to change anything at the caller. This
lesson looks at how that limit is overcome: taking a variable's **address** means passing that
address instead of a copy to the called side — and the address is itself also a value.

A pointer (`*T`) is a type in Go that holds a variable's place in memory. The notation `&x`
produces `x`'s address, the notation `*p` reads from or writes to the place a pointer points
to. These two operators — taking an address and dereferencing — stand under everything this
lesson measures: whether a call changes the caller depends on which of the two was written and
when.

#### When Taking an Address Is Necessary

The four types in this topic give four separate answers to the same question: when a value
passed to a function is changed, does that change reach the caller without taking an address,
or is an address required?

```go
// address.go — whether taking an address is needed for an array, a slice and a struct
package main

import "fmt"

type Record struct {
	Number int
}

func changeArray(d [3]int)      { d[0] = 99 }
func changeArrayPtr(d *[3]int)  { d[0] = 99 }
func writeSliceElement(s []int) { s[0] = 99 }
func writeMap(s map[string]int) { s["a"] = 99 }
func changeStruct(k Record)     { k.Number = 99 }
func changeStructPtr(k *Record) { k.Number = 99 }

func main() {
	array := [3]int{1, 2, 3}
	changeArray(array)
	fmt.Println("array, no address    :", array)

	array2 := [3]int{1, 2, 3}
	changeArrayPtr(&array2)
	fmt.Println("array, with address  :", array2)

	slice := []int{1, 2, 3}
	writeSliceElement(slice)
	fmt.Println("slice, no address    :", slice)

	dict := map[string]int{"a": 1}
	writeMap(dict)
	fmt.Println("map, no address      :", dict["a"])

	record := Record{Number: 1}
	changeStruct(record)
	fmt.Println("struct, no address   :", record)

	record2 := Record{Number: 1}
	changeStructPtr(&record2)
	fmt.Println("struct, with address :", record2)
}
```

```
array, no address    : [1 2 3]
array, with address  : [99 2 3]
slice, no address    : [99 2 3]
map, no address      : 99
struct, no address   : {1}
struct, with address : {99}
```

Six lines show the different answer four composite types give to the same question. An array
and a struct do not change when passed without taking an address — both are values that are
copied entirely, the same rule established in the Arrays and Slices and Structs lessons. A
slice and a map, on the other hand, can change **without ever taking an address**:
`writeSliceElement` and `writeMap` took only the slice and the map themselves (their headers),
not a pointer, but both changed the data at the caller — because what changed was not the
header, it was the shared backing array or hash table the header points to. The sharing
measured in the Slice Operations and Maps lessons explains here **why** taking an address is
unnecessary: both already carry a pointer within themselves.

The reason taking an address is necessary for an array and a struct is the exact opposite:
neither carries any pointer within itself, what they carry is the content itself. Taking a
pointer is the **only** way to prevent this content from being copied. The four types thus
split into two groups: an array and a struct **carry their own content** and demand an address
when they are to be changed; a slice and a map **carry a pointer** and even their own copy is
enough when they are to be changed — growth itself (`append`, or a map's hash table being
built for the first time) is a separate matter, of course, because in that case what changes
is not the content, it is the header itself.

#### Reading From a nil Pointer Is Left Open

A pointer becomes `nil` when declared without an initial value; this was one of the three
cases left **open** in the Zero Values lesson's binding distribution. Reading the `nil`
pointer itself (looking at what the address is) is not a problem, but trying to go to the
place it points to — accessing its field — stops the runtime.

**CT14.** — The measurement separates two distinct questions: is the pointer itself `nil` (a
question that can be safely asked), and what is the field at the place the pointer points to
(a question that cannot be safely asked). The two are not mixed in the same line.

```go
// nil.go — reading from a nil pointer is left open
package main

import "fmt"

type Record struct {
	Number int
}

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

func main() {
	var pointer *Record
	fmt.Println("the pointer itself is nil:", pointer == nil)

	readPanicked := panics(func() { _ = pointer.Number })
	fmt.Println("reading pointer.Number    : panic=", readPanicked)
}
```

```
the pointer itself is nil: true
reading pointer.Number    : panic= true
```

The question `pointer == nil` itself is answered safely, because this is reading the
pointer's **own** value — an address, or the absence of one. But the notation
`pointer.Number` requires first **going** to that address, and then reading the `Number` field
there; if there is no address to go to, this step stops at run time. The difference from the
`nil` map write panic measured in the Maps lesson is this: in a map, what panicked was only
**writing**; reading was defined; in a pointer, what panics is **reading itself**, because in
a map "the looked-for place does not exist" could be tied to a defined outcome (the zero
value), while in a pointer "there is no place to go to" has no defined counterpart.

This difference shows why the two types interpret their `nil` state differently. A map's
`nil` state meant "I do not have any hash table yet, but I can still search" — the search
already produced a defined answer (the zero value, `false`) for a key that is not found. A
pointer's `nil` state, on the other hand, means "there is no place I point to at all"; there
is no defined zero value to fall back on here, because what a pointer needs to read is not its
own value, it is the value at the place it points to. This is exactly the common point of the
three `open` cases in the Zero Values lesson: a defined counterpart exists only when the
**structure** of the question being asked allows for it.

#### Four Calls: Address or Copy

In addition to functions, a type's **methods** can also be defined with a value receiver or a
pointer receiver; both are called with dot syntax, and which one is used is not visible from
the call notation. If a method definition has the form `func (k Record) Name()`, the receiver
is a value; if it has the form `func (k *Record) Name()`, the receiver is a pointer — this
single character in the parentheses (`*`) establishes, for methods, exactly the same
distinction just measured for function parameters. The measurement here deliberately reuses
the single-field `Record` type from the Structs lesson, to make it visible that what creates
the difference is not the number of fields, only the receiver's definition.

```go
// fourcalls.go — four calls, two of them change the caller's value
package main

import "fmt"

type Record struct {
	Number int
}

func changeByCopy(k Record)     { k.Number = 99 }
func changeByPointer(k *Record) { k.Number = 99 }
func (k Record) ChangeV()       { k.Number = 99 }
func (k *Record) ChangeP()      { k.Number = 99 }

func main() {
	k1 := Record{Number: 1}
	changeByCopy(k1)
	fmt.Println("value-parameter function      :", k1.Number)

	k2 := Record{Number: 1}
	changeByPointer(&k2)
	fmt.Println("pointer-parameter function    :", k2.Number)

	k3 := Record{Number: 1}
	k3.ChangeV()
	fmt.Println("value-receiver method         :", k3.Number)

	k4 := Record{Number: 1}
	k4.ChangeP()
	fmt.Println("pointer-receiver method       :", k4.Number)
}
```

```
value-parameter function      : 1
pointer-parameter function    : 99
value-receiver method         : 1
pointer-receiver method       : 99
```

**Two** of the four calls change the caller's value; the goal is not to find these two, it is
to show that changing the caller comes **from the receiver's definition, not from the call
notation**. The first two repeat the expected pattern: a value parameter gets its own copy, a
pointer parameter reaches back to the caller. The last two show the same pattern as a
**method call**: `k3.ChangeV()` and `k4.ChangeP()` are syntactically almost identical — both
in the form `variableName.MethodName()` — but one is defined with a value receiver, the other
with a pointer receiver, and their results differ. Looking at the call, it is not known which
will run; what determines this is the receiver type in the method's **own** definition.

The notation `k4.ChangeP()` itself contains no pointer, but the compiler **itself** takes
`k4`'s address and expands it to `(&k4).ChangeP()` — because `k4` is an addressable variable.
This automatic conversion makes it possible to call a pointer-receiver method through an
ordinary value; the address-taking step is not written, but it happens.

This automatic conversion has a limit: it only works for **addressable** values. A variable
is addressable because it has a permanent place in memory; it is not a temporary value like a
function's return value. `k4.ChangeP()` worked because `k4` was a variable; a call like
`Record{Number: 1}.ChangeP()` would not compile, because the value built with curly braces is
not itself addressable. The same automatic conversion also works in reverse: when a
value-receiver method is called through a non-`nil` pointer of a pointer-receiver type, the
compiler **dereferences** the pointer on its own.

This two-way automatic conversion turns defining a type's methods with always the same
receiver kind into a practical habit: if some of a type's methods have a value receiver and
others a pointer receiver, the caller has to memorize method by method which call changes the
value it calls on and which does not. The `ChangeV`/`ChangeP` pair above deliberately displays
this confusion; the two existing side by side on a real type produces exactly the ambiguity
this measurement shows.

Receiver choice does not depend only on the question of "wanting to change the caller or
not." A value receiver produces a **full copy** of that value every time the method is
called — for a small type carrying a single integer, like `Record`, this cost is negligible,
but for a large type with many fields or embedding other structs, as in the Structs lesson, it
means copying every field on every call. A pointer receiver, because the only thing carried is
an address, makes this copying independent of the struct's size. Even a method that does not
want to change the caller can choose a pointer receiver purely to avoid the copying cost.

#### Limiting Measure: A Pointer Is Also a Value

Writing to the place a pointer points to and reassigning the pointer **itself** are different
things. In Go, everything is passed by value (established as a concept in the Programming
Fundamentals course, not repeated here) — a pointer is no exception either, it is itself also
a value, and a pointer parameter is copied too.

**CT15.** — Both functions take an integer pointer, and both look almost identical in the
source; the difference is in a single line. The measurement deliberately places these two
lines side by side so that the difference can be read from the text alone, with no need to run
it — then the two are run separately to confirm that this reading is correct.

```go
// value.go — a pointer is itself also a copied value
package main

import "fmt"

func reassign(p *int) {
	fresh := 99
	p = &fresh // changes only the local copy
}

func writeValue(p *int) {
	*p = 99 // writes to what the pointer points to
}

func main() {
	x := 1
	reassign(&x)
	fmt.Println("reassigning the pointer        :", x)

	y := 1
	writeValue(&y)
	fmt.Println("writing to what it points to   :", y)
}
```

```
reassigning the pointer        : 1
writing to what it points to   : 99
```

When `reassign` is called, `x`'s address is copied into `p`; when `p = &fresh` is written
inside the function, what changes is only `p`'s **own copy** — it now points to a different
address, but the caller's `x` is not affected at all, because `x` itself never took `fresh`'s
address. `writeValue`, on the other hand, writes directly to the address `p` points to with
`*p = 99`; because this address is still the same as `x`'s (`y`, in this call), the write
reaches the caller.

This difference explains **why** the "changes once a pointer is taken" rule in the four-call
measurement is correct. A pointer-parameter function changing the caller does not come from
the pointer being some magic "reference," it comes from the **address the pointer carries**
being copied and then written to at the copied address. As long as the place it points to
stays the same even when the address is copied, a write made to that place is visible from
every copy — just as a slice's backing array continues to be shared when its header is
copied. A pointer is a value that continues sharing when copied; its difference from a slice,
unlike a slice, is that what it points to is not a backing array, it is a **single value**.

The four types measured throughout this lesson — array, slice, map, struct — and the pointer
itself are five appearances of one rule: in Go, assignment, parameter passing and returning
always do the same job, they copy a value. The apparent difference comes from **what** the
copied value carries. An array and a struct carry content, and the copy copies the content
too. A slice, a map and a pointer all carry an address, and the copy copies the address, not
the place it points to — this is why sharing continues. "Passing by reference" never existed
as a separate mechanism; what existed was some values carrying a pointer and others not.

#### Summary

- An array and a struct do not change when passed without taking an address, because both
  are copied entirely; a slice can change even without taking an address, because it already
  carries a pointer within itself.
- Reading a `nil` pointer itself is safe; trying to go to the place it points to (accessing a
  field) stops the runtime — an example of the open class from the Zero Values lesson.
- Only two of four calls (value parameter, pointer parameter, value-receiver method,
  pointer-receiver method) change the caller's value; the distinction is read not from dot
  syntax, but from the receiver's definition.
- A pointer is itself also a value and is copied: reassigning a pointer changes only the
  local copy, writing to what it points to (`*p = ...`) reaches the caller.

#### Next Step

The types measured in this lesson — array, slice, struct, pointer — always carried a number
or a named field; none of them carried text within itself. Text is a composite type different
from these: a string, a value that looks like a single piece from the outside but is a byte
array inside. Because a string is also immutable, it stays outside the copy/sharing
distinction measured here, but it carries its own unwritten question. The next lesson asks
this question: what does a string's length count, what does indexing a string return, and
when do these two diverge from the intuition of the word "character"?

### Strings and Runes

Source: https://academia.sh/en/courses/go-fundamentals/strings-and-runes

A Go string is a byte array: length counts bytes, indexing returns a byte, only range gives a rune. The same text's byte count, rune count and first byte are measured separately from each other, and the string is shown to be immutable. The limiting measure shows that rune count is also not character count: combining marks can produce text that looks the same with different rune counts.

The types measured throughout this topic — array, slice, map, struct, pointer — always
carried a number, an address, or a named field. A string asks a question separate from these:
a piece of text that looks like a single unit from the outside, what does it carry **inside**?
The answer is this topic's closing question: what does a string's length count, what does
indexing a string return?

Go's string type stands under every text-processing tool in the language's standard
library: a file path, a network request's body, user input — all are carried as strings. Not
knowing **what** this type carries means not knowing which unit these tools operate in (bytes,
or code points).

A string is also a composite type, but it differs from the others in this topic in two ways.
First, every type measured so far (except the array) was changeable in some way — a slice
could be written to, a map could be added to, a struct's field could be updated; a string
cannot be changed in place in any way. Second, the data a string carries is not meaningless
bytes on its own, it is a byte array interpreted according to an **encoding rule** (UTF-8);
this rule directly determines what length and indexing mean.

#### A String Is a Byte Array

In Go, a string is a read-only byte array. Because the source file is UTF-8, string
**literals** are UTF-8, but the language does not guarantee that a string at run time carries
valid UTF-8. When `len` is applied to a string, it gives the byte count; indexing (`text[i]`)
returns a single byte — not a character. This was one of the three cases left **open** in the
Zero Values lesson's binding distribution: a string's length is defined, but what comes back
when it is indexed is a byte, not a character.

**CT16.** — `"élan"` was chosen because it contains a letter, `é`, that cannot be encoded in a
single byte; a text made only of ASCII letters would never show this measurement's byte/rune
difference.

```go
// byte.go — a string's length and indexing operate on bytes
package main

import "fmt"

func main() {
	text := "élan"
	fmt.Println("length (bytes):", len(text))
	fmt.Println("first byte    :", text[0])
	fmt.Println("rune count    :", len([]rune(text)))
}
```

```
length (bytes): 5
first byte    : 195
rune count    : 4
```

`"élan"` looks like four letters, and `len` gives `5`, because the letter `é` is encoded in
UTF-8 as **two** bytes (`195` and a following byte), while `l`, `a` and `n` each take one
byte. `text[0]` is not a character, it is the numeric value of `é`'s first byte (`195`) —
`fmt.Println` prints this as an integer, not as a piece of the string. The conversion
`[]rune(text)`, on the other hand, splits the string into code points and finds four runes —
this is the number that overlaps with the visible letter count, not the byte count.

These three numbers (`5`, `195`, `4`) need to be read independently of each other.
`len(text)` always counts bytes, no matter how many letters appear visible; in a pure-ASCII
string, byte count and rune count overlap, but this is a coincidence — because every ASCII
character is already encoded in a single byte. In a string containing French, Greek,
Cyrillic, or any multi-byte alphabet, this overlap breaks down; `"élan"` being four letters but
five bytes is exactly this breakdown itself.

#### A String Is Immutable

The question left at the end of the Pointers lesson finds its answer here: a string stays
outside this topic's copy/sharing distinction, because a string is **immutable**. It is not
possible to change even a single byte of a string in place. This is the most visible
difference between a slice and a string: both carry a header leading to a byte (or element)
array, but while writing to the backing array a slice's header points to is free, writing to
the byte array a string's header points to is blocked by the language itself.

```go
// immutable.go — writing to a byte of a string is rejected
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() {
	write := `package trial
	func F() {
		s := "abc"
		s[0] = 'x'
		_ = s
	}`
	fmt.Println("writing to a string index:", compiles(write))
}
```

```
writing to a string index: false
```

`s[0]` is a valid notation for **reading** a byte, but `s[0] = 'x'` is rejected — the compiler
does not allow writing to a string's elements. This is fundamentally different from writing to
an array index in the Arrays and Slices lesson: writing to an array element is valid, writing
to a string byte is never valid. Code that wants to "change" a string actually produces a
**new** string — it leaves the original as it is and reassigns a changed copy to a variable.
This is why a string is never subject to the "does writing to a derived piece also change the
source" question measured in any lesson in this topic — writing itself is not possible.

**CT17.** — This measurement is also run with a compiler call, because the rejection comes
at compile time; it is not a panic left to run time.

Code that wants to change a string at the byte level produces a **separate** copy with a
`[]byte` conversion; that copy is changeable, but the original string is not affected by it.

```go
// convert.go — a []byte conversion produces a separate copy
package main

import "fmt"

func main() {
	text := "élan"
	bytes := []byte(text)
	bytes[2] = 'X' // the third byte is the letter 'l', occupying a single byte
	fmt.Println("changed copy   :", string(bytes))
	fmt.Println("original string:", text)

	part1 := "élan"
	joined := part1 + " vital"
	fmt.Println("joined string    :", joined)
	fmt.Println("part1 unchanged? :", part1 == "élan")
}
```

```
changed copy   : éXan
original string: élan
joined string    : élan vital
part1 unchanged? : true
```

The notation `[]byte(text)` copies `text`'s bytes into a **new** slice; this slice can be
changed like an ordinary slice, and making its third byte `'X'` changes the slice's own data.
But `text` is not affected by this at all, because the conversion is a copy, not sharing.
Joining with `+` is similar: `part1 + " vital"` produces a new string, `part1` itself never
changes. Like the array in the Arrays and Slices lesson, a string can never be written back to
its source through any "derived" path either — this is exactly where it departs from a slice
and a map.

#### range: Byte Index, Rune Value

`len` and indexing worked at the byte level; iterating over a string with `range`, on the
other hand, behaves differently. `range` decodes the string according to UTF-8 rules and gives
a **rune** at every step, not a single byte. `len` counts bytes, `text[i]` gives a byte,
`range` gives a rune — all three read from the same data, in three separate units.

```go
// iterate.go — range gives a rune in a string, not a byte
package main

import "fmt"

func main() {
	text := "élan"
	for i, r := range text {
		fmt.Printf("  byte-index=%d rune=%q code-point=U+%04X\n", i, r, r)
	}
}
```

```
  byte-index=0 rune='é' code-point=U+00E9
  byte-index=2 rune='l' code-point=U+006C
  byte-index=3 rune='a' code-point=U+0061
  byte-index=4 rune='n' code-point=U+006E
```

Four steps give four runes — not the single byte (`195`) `text[0]` gives, but a full code
point (`é`, U+00E9). The printed indices are not consecutive either: `0`, `2`, `3`, `4` — the
second step skips `1`, because `é` takes two bytes and `range` jumps to the next rune's
**byte position**. `rune` is the language's own type name and is an alias for `int32`; it
carries a Unicode code point. The code point itself is a concept closed in the How Computers
Work course; what is measured here is how Go realizes this concept under the name `rune` and
with `range`'s automatic decoding.

This automatic decoding is unique to `range`; if the string is iterated by being
**converted** to another composite type first, the same decoding is not applied. Doing `range`
over `[]byte(text)` gives a different result than doing `range` over `text` itself.

```go
// byteiter.go — range over []byte walks byte by byte
package main

import "fmt"

func main() {
	text := "élan"
	for i, b := range []byte(text) {
		fmt.Printf("  index=%d byte=%d\n", i, b)
	}
}
```

```
  index=0 byte=195
  index=1 byte=169
  index=2 byte=108
  index=3 byte=97
  index=4 byte=110
```

Here there are five steps, not the four in the previous section; the indices are
`0, 1, 2, 3, 4` — consecutive, with no gaps. `range`'s behavior of decoding into runes applies
only when the value given to `range` is a **string**; when the same data is converted to
`[]byte` and given instead, `range` assumes it is walking an ordinary slice and gives the
bytes one by one. The difference is not in the source data itself, it is in the **type** given
to `range`.

#### Limiting Measure: Rune Count Is Also Not Character Count

`len([]rune(text))` gave the visible letter count, and this looks more reliable than the byte
count. But rune count also does not always overlap with "character count," because in
Unicode, what looks like a single mark on screen can also be written as a combination of
**more than one** code point.

**CT18.** — Both strings carry four letters that look exactly the same in the source (`c`,
`a`, `f`, an `e`-derived mark); the measurement separates the two only at the code point
level, not in what is displayed on screen.

```go
// combining.go — text that looks the same, two different rune counts
package main

import "fmt"

func main() {
	precomposed := "café"      // e and the accent combined into a single code point: U+00E9
	decomposed := "cafe" + "́" // e separate, accent a separate code point: U+0065 U+0301
	fmt.Println("precomposed :", precomposed, " rune count=", len([]rune(precomposed)), " bytes=", len(precomposed))
	fmt.Println("decomposed  :", decomposed, " rune count=", len([]rune(decomposed)), " bytes=", len(decomposed))
	fmt.Println("byte-equal? :", precomposed == decomposed)
}
```

```
precomposed : café  rune count= 4  bytes= 5
decomposed  : café  rune count= 5  bytes= 6
byte-equal? : false
```

Both strings look **the same** on screen — both write "café." But `precomposed` uses a single
precomposed code point (`é`, U+00E9), while `decomposed` follows a plain `e` with a separate
**combining accent** code point (U+0301); the two produce the same mark visually, but with a
different number of code points. This is why `precomposed` counts four runes, `decomposed`
counts five, and they do not come out equal when compared with `==` — string comparison is
done byte by byte, it does not look at visual equivalence. The rule measured in the Arrays and
Slices lesson, that `==` looks at content in an array, applies here too: string comparison
also looks at the exact equality of content (the bytes), not how it looks on screen. Even when
two strings are visually indistinguishable, `==` counts them as different if they have
different code point sequences. This is a concrete problem code working with user input can
run into: two texts that look the same may not come out equal when compared with `==` if their
sources differ (one may have come from a keyboard, one from a file, one from another system) —
the cause of the error is not in the comparison operator, it is in the two inputs having been
encoded with different code points from the start.

This limit shows that the unit of "the mark you see" has no counterpart in the language
itself. The **grapheme** concept closed in the How Computers Work course names exactly this:
one or more code points coming together to form what the user sees as a single mark. Go's
standard library offers no tool beyond counting runes; counting graphemes requires a separate
step that applies code point combination rules, and that step is outside this measurement's
scope.

The three units of measure thus answer three separate questions, and none of them
substitutes for the others. Byte count tells how much room a string takes up in memory or over
a network — storage and transfer matter here. Rune count tells how many code points follow one
after another — the boundary of the language's own type system is here. Grapheme count, on the
other hand, tells how many "marks" a user sees on screen, but this information never stands in
the language's type system; it cannot be obtained just by looking at the string itself. Code
that does not know `len` counts bytes can cut a string in the middle and produce an invalid
UTF-8 sequence; code that does not know rune count is not grapheme count can also misapply a
rule like "limit to five characters" on text with combining marks. Both come from the same
root: confusing the string's **own** unit of measure with the unit of measure the user
**sees**.

#### Summary

- A Go string is a byte array: `len` counts bytes, indexing returns a byte — this was one of
  the three open cases from the Zero Values lesson.
- A string is immutable; writing to a byte is rejected at compile time. "Changing" always
  means producing a new string; even a `[]byte` conversion does not share the original, it
  produces an independent copy.
- Iterating with `range` gives not a byte but a **rune**; byte indices may not be consecutive
  at multi-byte code points.
- Although rune count looks like a more reliable "character" measure than byte count, text
  that looks the same can produce different rune counts with combined or separate code
  points — what we call a "character" has no direct counterpart in the language, and the
  grapheme concept names this gap.

#### Next Step

This topic began with the array and closed with the slice, map, struct, pointer, and finally
the string; each answered its own unwritten question in a different direction — some copied
(array, struct), some shared (slice, map, pointer), some could not be written to at all
(string). The six lessons' common question was always the same: when a value is carried
somewhere, what gets copied, what gets shared? The language's type system tells you what a
string is: a byte array, with a specific encoding, an immutable value. But the type system
does not tell you what a string **means** — which visible mark the code points form, what a
name represents.

The next topic carries the same question **outside** the source. Everything measured so far
stood inside a single file, a single package — every example stayed in `package main`, in a
single directory; no measurement asked whether a name would be visible from code in another
directory. But a real program is not made of a single file: it spreads across several files,
several packages, several dependencies. Who determines whether a name can be seen from another
file, which version of a dependency gets chosen, and how an unwritten formatting rule gets
applied? These questions now belong not to the language's type system, but to the package and
module mechanism.

## Packages and Tooling

Exporting depending on a single letter and package layout's counterpart in visibility, the module file's selection of an unwritten version, the language's own command family enforcing unwritten rules, the conventions that documentation generation from the source depends on.

### Package Layout and Visibility

Source: https://academia.sh/en/courses/go-fundamentals/package-layout-and-visibility

Whether an export is visible from one package to another is tied to the case of a single letter, shown by running the compiler from within a program across four trials: an uppercase function and a struct constructed only with its exported field compile; a lowercase function and an attempt to read a struct's unexported field from another package do not. The limiting measurement shows that two separate files of the same package share each other's lowercase name without any trouble at all — the boundary of visibility is not the file, it is the package.

The previous lesson's closing established that what a string is is stated by the language's
type system, but what it means is not. This lesson carries the same question outside the
source: who determines a name's visibility, the selection of a version, and the application of
a convention? The first stop is visibility. Go has no keyword to write for making an identifier
accessible to another package — there is no `public`, `private`, or similar declaration. The
only thing written is the name, and whether the **first letter of that name is uppercase or
lowercase** decides everything. This lesson's question is the same as the course's general
question: when you do not write this — that is, when you do not write the letter as uppercase —
what happens?

This question's place in the course's general framework is a little different. In the previous
topic, the unwritten thing was always a *value* — a variable given no initial value, a return
left unused. Here the unwritten thing is an **access decision**: not writing a name with an
uppercase letter. The answer, too, becomes dependent on position — the same lowercase name works
in a defined way inside its own package, but is rejected when called from outside the package.
While the rest of the course had a single class corresponding to a single outcome, in this lesson
the counterpart of the same unwrittenness changes **depending on where it is viewed from** — the
measurement, too, is built accordingly by calling the same name from two different positions.

#### The Rule of Exporting: One Letter

**PT1.** Go carries no separate declaration syntax for visibility; whether an identifier is
visible from outside the package is determined solely by whether the first letter of the name is
uppercase (**exported**) or lowercase (**unexported**). **PT2.** This rule is not specific to a
single kind of identifier — a function, a type, a variable, a constant, and a struct's field all
pass through the same rule, looking at the same letter.

This resembles the design decision built in the previous lesson around the zero value: the
language **itself** determines the counterpart of something unwritten. The difference is in
direction: the zero value filled an unwritten *value*; the export rule fills an unwritten
*access declaration* — and when nothing at all is written besides the case of the letter, the
compiler always reads this as "not exported." The decision itself is not a choice, it is a
mandatory consequence of how the name is written.

The rationale for this design is not a mere shortcut. A separate visibility declaration (like
`public`/`private`) asks for a second decision for every identifier: choose the name, then choose
the visibility. Go removes this second decision by embedding it in the name itself — changing an
identifier's visibility means changing its name, and the two can never be separated from each
other. One consequence of this decision is that visibility is not a run-time check at all, it is
a syntax rule resolved entirely at compile time. The measurement below tests exactly this,
without performing a second run at all, only by calling the compiler — a visibility violation
appears not as an exception or an error value, but as the compilation itself stopping.

#### The Area a Letter Covers

The same rule is applied silently to other kinds of identifiers built over the past three
lessons as well. In the previous topic, constants ordered with `iota` are exported if written in
uppercase and stay confined to their own package if written in lowercase — there is no separate
rule for the constant's **value**, the same rule applies only to its name. The name of a method
bound to a struct passes the same test too: even if its receiver (a concept built in the
Composite Types topic) is an exported type, if the method's own name starts with a lowercase
letter, that method can only be called from its own package. What a package offers to the
outside can therefore be read by looking at individual names: someone scanning a package's
source does not search for a separate declaration list to see which function, type, constant, or
method is accessible from outside — they only look at the first letters. This readability is not
a side effect, it is the rule's actual purpose: a package's public surface becomes information
that can be extracted from the source itself alone, with no other document to consult.

#### Calling the Compiler with a Two-Package Setup

The procedure running since the course's first lesson expands here: the compiler is tested by
actually compiling the source, the error text is never read, only the compiled/did-not-compile
result is taken. That lesson's setup tested a single package; this lesson builds a **two-package**
setup, because the place where visibility is actually tested is the package boundary itself. The
routines below write a library package to a separate subdirectory, the calling side to the
module's root, and compile the two together.

```go
// tools/tools.go — shared routines that call the compiler from within the program
package tools

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

// TwoPackagesCompile writes librarySource to the "trial/library" subpackage and
// mainSource to the temporary module root, then compiles them: visibility between the
// two packages is tested by a real compiler call. The error text is not read, only success.
func TwoPackagesCompile(librarySource, mainSource 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.MkdirAll(filepath.Join(dir, "library"), 0o755)
	os.WriteFile(filepath.Join(dir, "library", "library.go"), []byte(librarySource), 0o644)
	os.WriteFile(filepath.Join(dir, "main.go"), []byte(mainSource), 0o644)
	return run(dir)
}

// SamePackageTwoFilesCompile writes two files of the same package (one defining, one
// reading) to a single temporary module's root directory and compiles them.
func SamePackageTwoFilesCompile(fileA, fileB 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, "a.go"), []byte(fileA), 0o644)
	os.WriteFile(filepath.Join(dir, "b.go"), []byte(fileB), 0o644)
	return run(dir)
}

func run(dir string) bool {
	cmd := exec.Command("go", "build", "./...")
	cmd.Dir = dir
	cmd.Env = append(os.Environ(), "GOTOOLCHAIN=local")
	return cmd.Run() == nil
}
```

`TwoPackagesCompile` writes a library source to a subpackage named `library`, and the source
calling it to the module's root — the only link between the two is the `import` line. The
compiler (appearing in the code block only as the argument of the `exec.Command("go", "build",
...)` call) runs against a completely new, empty directory every single time — the remnant of a
previous trial never affects the next one. Every trial sets up its own temporary module, because
otherwise a file left by one trial could muddy the next trial's result; a fresh directory
guarantees that what is measured is only the two sources written at that moment.
`SamePackageTwoFilesCompile` asks a different question: what happens if two files belong to the
**same** package? Outside these two routines the `tools` package knows nothing at all — which
name is "library," which is "main," which letter is uppercase, all of that stays outside this
package, in the source texts the calling side constructs. The `GOTOOLCHAIN=local` environment
variable is given in both routines as well, so the inner compiler invocation does not try to
download its own toolchain from the network, only uses what is already installed.

#### Five Trials

The measurement below writes the same function and the same struct in several forms and tests
them between two packages: one function name, two spellings of a struct literal, and one field
read.

```go
// main.go — the rule of exporting and the boundary of a package are not the boundary of a file
package main

import (
	"fmt"

	"lesson/tools"
)

func show(question string, compiled bool) {
	fmt.Printf("%-53s %v\n", question, compiled)
}

func main() {
	libraryUpper := "package library\n\nfunc Add(a, b int) int { return a + b }\n"
	mainUpper := "package main\n\nimport \"trial/library\"\n\nfunc main() { _ = library.Add(2, 3) }\n"
	show("Add (uppercase), called from another package", tools.TwoPackagesCompile(libraryUpper, mainUpper))

	libraryLower := "package library\n\nfunc add(a, b int) int { return a + b }\n"
	mainLower := "package main\n\nimport \"trial/library\"\n\nfunc main() { _ = library.add(2, 3) }\n"
	show("add (lowercase), called from another package", tools.TwoPackagesCompile(libraryLower, mainLower))

	libraryStruct := "package library\n\ntype Record struct {\n\tName   string\n\tsecret int\n}\n"
	mainStructNamed := "package main\n\nimport \"trial/library\"\n\nfunc main() { _ = library.Record{Name: \"x\"} }\n"
	show("Record{Name: \"x\"}, constructed with the named field", tools.TwoPackagesCompile(libraryStruct, mainStructNamed))

	mainStructPositional := "package main\n\nimport \"trial/library\"\n\nfunc main() { _ = library.Record{\"x\", 0} }\n"
	show("Record{\"x\", 0}, constructed positionally", tools.TwoPackagesCompile(libraryStruct, mainStructPositional))

	mainStructRead := "package main\n\nimport \"trial/library\"\n\nfunc main() { r := library.Record{Name: \"x\"}; _ = r.secret }\n"
	show("r.secret, read from another package", tools.TwoPackagesCompile(libraryStruct, mainStructRead))

	fmt.Println()
	fileA := "package library\n\nvar value = 42\n"
	fileB := "package library\n\nfunc Read() int { return value }\n"
	show("value (lowercase), read from another file of the same package", tools.SamePackageTwoFilesCompile(fileA, fileB))
}
```

```
Add (uppercase), called from another package          true
add (lowercase), called from another package          false
Record{Name: "x"}, constructed with the named field   true
Record{"x", 0}, constructed positionally              false
r.secret, read from another package                   false

value (lowercase), read from another file of the same package true
```

**PT3.** Two of five cross-package trials compile, three do not; what determines the split is,
every single time, the first letter of the name tried, or whether the spelling implicitly
touches that letter. **PT4.** `Record{Name: "x"}` — the literal constructed with the named field
— compiles: the `Record` struct having an unexported `secret` field does not prevent using the
struct itself or only its exported fields. **PT5.** The same struct fails to compile when
constructed positionally with `Record{"x", 0}`, that is, without writing the field name; this
spelling implicitly assigns a value to the second position, the `secret` field, and the compiler
rejects this as an assignment made from outside the package to an unexported field. **PT6.** The
`r.secret` read also fails to compile — the same instance of the same struct is rejected when a
different field is requested.

Reading the third and fourth lines together (the named literal, the positional literal) shows
that the export rule operates **per field** and is **sensitive to the spelling** used: even if a
struct is exported as a whole (`Record` is already exported here since it starts with an
uppercase letter), each of its fields is evaluated separately according to its own letter. The
named literal says it is writing only the `Name` field, and never touches `secret` at all — the
compiler accepts this and leaves `secret` its own zero value. The positional literal, on the
other hand, requires assigning a value to **every** field in order; the second position falls on
the `secret` field, and this, even though it is not written in the source text, is equivalent in
the compiler's eyes to writing to an unexported field from outside the package. Reading a field
of the record (`r.secret`) belongs to the same family: mentioning the name from outside the
package, whether reading or implicitly writing, is rejected either way. The compiler prints no
error text in any of the three cases — only compiled/did-not-compile is read — but the common
cause of all three is the same: mentioning, in a source text, a name not visible from outside the
package.

#### The Boundary Is the Package Boundary, Not the File Boundary

**PT7.** Two separate source files of the same package, with no `import` at all between them,
directly share each other's unexported name: `a.go` defines the lowercase `value` variable,
`b.go` reads it directly, and both compile.

This is the lesson's limiting measurement. We said the visibility rule looks at the case of the
letter, but the answer to **what** this rule is applied against is not "the file." In the
measurement above, `a.go` and `b.go` look like two separate files, written in two separate
compilation units — but both carry the `package library` declaration, and this single line puts
both of them, in the compiler's eyes, into **one single unified namespace**. Because `value`
starts with a lowercase letter, it is never visible from outside the package at all, but it is
directly visible from any file of the same package, with no `import` written at all.

This distinction is often overlooked because in many languages the compilation unit is the file
itself, and the names a file sees are limited to that file's own declarations. In Go the
compilation unit is the **package**; how many files a package is split into is a decision made
purely for organizational convenience, and it never changes visibility at all. Splitting a
package into ten files versus keeping it in a single file gives **exactly the same** result in
terms of which names that package exports and does not export — the only difference is which
file the person reading the source looks at, not the set of names the compiler sees.

This has a practical consequence: splitting a package into multiple files is purely for
readability, and causes no loss of access at all. Real packages are usually split by topic for
this reason — one file carries the types, another the functions over those types, a third the
error values — and these files share each other's unexported names with no extra declaration
written at all. The measurement confirms this from the reverse direction too: there is no bridge
code at all carrying `value` between `a.go` and `b.go`, because no bridge is needed — the moment
the two share the `package library` line, they enter the same namespace. If the boundary of
visibility were the file, the cost of splitting a package into multiple files would be setting up
a new "internal API" every single time; in Go this cost does not exist at all, because the
boundary is already the package's boundary, not the file's.

This is not the same thing as scope (a concept built in the Programming Fundamentals course).
Scope determines *where* a name can be seen — at a block, function, or package level; the export
rule, by contrast, decides whether a name already visible at the package level can be seen from
**outside** that package. `value` belongs to the same package scope in every file of the package;
the export rule does not change this scope itself, it only determines whether that scope can
**cross** the package boundary.

#### Summary

- Go has no separate declaration syntax for visibility; whether a name is exported is determined
  solely by whether its first letter is uppercase or lowercase.
- This rule works the same way for a function, a type, a variable, a constant, and a struct
  field; even if a struct is exported as a whole, each of its fields is evaluated separately
  according to its own letter.
- Two of five cross-package trials (an uppercase function, a struct literal constructing only
  the exported field) compile; three (a lowercase function, a positional literal's implicit
  hidden-field assignment, reading an unexported field) do not.
- The limiting measurement: the compilation unit is the package, not the file; two separate
  files of the same package share each other's lowercase name with no `import` at all.
- The compiler was again called from within the program in this lesson too, and only the
  compiled/did-not-compile result was read; the error text was never printed in any measurement.

#### Next Step

In this lesson, where the boundary stands — in the package, not the file — was measured by
running it, and confirmed across six separate trials that visibility is resolved entirely at
compile time, just by looking at a single letter. But a package is not sufficient on its own; it
is often dependent on other packages, and the packages a module sees do not always sit inside
that same module — they come from a separate module and carry a **version**. Which line, in what
form, that version is written at in the source is most often never stated at all; sometimes it is
written directly, sometimes it is derived from a dependency's own dependency. The next lesson
takes up this question: when a dependency's version is not written, what does the module file
select in its place, by what rule is this selection made, and why is the selected version not
always the newest version available?

### Modules and Dependencies

Source: https://academia.sh/en/courses/go-fundamentals/modules-and-dependencies

A dependency's version is most often never written in the source at all; of six requests, three are written in the main module's own source, three are derived from a dependency's own dependency. The module file's rule for filling in an unwritten version — minimal version selection — is modeled and run within the source; no network call is made. The limiting measurement shows that the selected version is not the newest version in a module's catalog, only the largest of the requests actually written.

The previous lesson showed that a package's boundary stops at the package, not at the file. This
lesson carries the same question one level up: when a package depends on another package, where
in the source is **which version** of that dependency meant? Most often, nowhere at all. A
module file writes the version of a handful of modules it directly depends on; the version of
those modules' own dependencies is never written at all, it is derived from the modules' own
files. This lesson's question is the same as the course's general question: when the version is
not written, what does the module file put in its place?

A dependency graph has a problem its version resolution has to solve: if the same module is
requested through more than one path in the graph, **which** version that module should be
compiled with is not written on any single line of the source. When two separate dependencies
request the same third module with different minimum versions, the main module is forced to
choose between the two — but this choice does not appear in the main module's source at all,
because the main module never mentions that third module directly. This situation comes up often
in dependency graphs by another name: two separate paths arrive at the same third point, and two
different requests collide there. The collision is not an error — both requests say "at least
this version," both draw only a lower bound, neither draws an upper bound. The job of the
selection is to merge these two lower bounds into **a single version** and make sure the merged
version satisfies both requests. The measurement below builds this situation in its smallest
form and reads the result by actually running the selection algorithm.

#### Six Requests, Two Sources

The measurement below never connects to a real module registry at all; the dependency graph is
built by hand within the source, and **it is written here explicitly that it is modeled**. There
are four modules: `libraryA`, `libraryB`, `libraryC`, `libraryD`. The main module directly
requests `libraryA`, `libraryB`, and `libraryD` — all three versions are written in the source.
`libraryA` requests one version of `libraryC`; `libraryB` requests another version of the same
`libraryC`. `libraryC`, for its own part, requests one version of `libraryD`. These three
requests are never written in the main module's source at all — they are **derived**, during the
main module's compilation, from the dependency modules' own files.

```go
// main.go — minimal version selection, the dependency graph is modeled in the source (no network call)
package main

import "fmt"

// Version carries a three-part version number; this lesson does not connect to a real
// module registry, the whole graph below is constructed by hand.
type Version struct {
	Major, Minor, Patch int
}

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

// greaterThan reports whether v is a newer version than other.
func (v Version) greaterThan(other Version) bool {
	if v.Major != other.Major {
		return v.Major > other.Major
	}
	if v.Minor != other.Minor {
		return v.Minor > other.Minor
	}
	return v.Patch > other.Patch
}

// Request carries the lowest version a module's dependency requests. If Written is
// true, the request sits directly in the main module's own source; if not, it was
// derived from another dependency's own module file.
type Request struct {
	Module    string
	From      string
	Requested Version
	Written   bool
}

func main() {
	requests := []Request{
		{Module: "libraryA", From: "main module", Requested: Version{1, 2, 0}, Written: true},
		{Module: "libraryB", From: "main module", Requested: Version{1, 1, 0}, Written: true},
		{Module: "libraryD", From: "main module", Requested: Version{2, 1, 0}, Written: true},
		{Module: "libraryC", From: "libraryA", Requested: Version{1, 4, 0}, Written: false},
		{Module: "libraryC", From: "libraryB", Requested: Version{1, 6, 0}, Written: false},
		{Module: "libraryD", From: "libraryC", Requested: Version{2, 0, 0}, Written: false},
	}

	fmt.Println("-- six requests, is the source written or derived --")
	writtenCount, derivedCount := 0, 0
	for _, req := range requests {
		source := "derived"
		if req.Written {
			source = "written"
			writtenCount++
		} else {
			derivedCount++
		}
		fmt.Printf("%-12s <- %-12s %-9s requested=%s\n", req.Module, req.From, source, req.Requested.text())
	}
	fmt.Printf("total: %d written, %d derived\n", writtenCount, derivedCount)

	fmt.Println()
	fmt.Println("-- selected version: the greatest of all requests for each module --")
	orderedModules := []string{"libraryA", "libraryB", "libraryC", "libraryD"}
	selected := map[string]Version{}
	for _, req := range requests {
		current, ok := selected[req.Module]
		if !ok || req.Requested.greaterThan(current) {
			selected[req.Module] = req.Requested
		}
	}
	for _, name := range orderedModules {
		fmt.Printf("%-12s selected=%s\n", name, selected[name].text())
	}

	fmt.Println()
	fmt.Println("-- limiting measurement: selection takes not the newest, but the greatest written --")
	catalogNewestC := Version{1, 9, 0}
	fmt.Printf("newest version in the catalog for libraryC: %s\n", catalogNewestC.text())
	fmt.Printf("selected version: %s\n", selected["libraryC"].text())
	fmt.Printf("is the selected version the same as the catalog's newest: %v\n", selected["libraryC"] == catalogNewestC)
}
```

```
-- six requests, is the source written or derived --
libraryA     <- main module  written   requested=1.2.0
libraryB     <- main module  written   requested=1.1.0
libraryD     <- main module  written   requested=2.1.0
libraryC     <- libraryA     derived   requested=1.4.0
libraryC     <- libraryB     derived   requested=1.6.0
libraryD     <- libraryC     derived   requested=2.0.0
total: 3 written, 3 derived

-- selected version: the greatest of all requests for each module --
libraryA     selected=1.2.0
libraryB     selected=1.1.0
libraryC     selected=1.6.0
libraryD     selected=2.1.0

-- limiting measurement: selection takes not the newest, but the greatest written --
newest version in the catalog for libraryC: 1.9.0
selected version: 1.6.0
is the selected version the same as the catalog's newest: false
```

**PT8.** Three of the six requests are written in the source, three are derived; which three are
written depends on whether that module is a **direct** dependency of the main module. **PT9.**
`libraryC` has two separate requests — one from `libraryA`, one from `libraryB` — and both come
into being without ever appearing in the source at all, purely from these two modules' own
files.

#### Minimal Version Selection

**PT10.** A module's selected version is the greatest of all the requests written for that
module; when both `1.4.0` and `1.6.0` are requested for `libraryC`, the selected version is
`1.6.0`, and when both `2.0.0` and `2.1.0` are requested for `libraryD`, the selected version is
`2.1.0`. The rule's name is **minimal version selection**, but the selected number is the
greatest of the requests, not the smallest — this seemingly contradictory naming resolves once
you look at what the rule actually guarantees. The rule says: let the selected version be **the
lowest version that violates no request**. A dependency requesting `1.4.0` also works with
`1.6.0` (versions are assumed backward compatible), but a dependency requesting `1.6.0` gives no
guarantee at all that it will work with `1.4.0`. This is why the lowest version satisfying **all**
requests is the greatest of the requests — the word "minimal" does not point to a single request,
it points to the single point that satisfies every request at once.

This is a computation in which backward compatibility is taken as an assumption; the rule itself
never checks this, it only accepts it. The `greaterThan` comparison above compares three numbers
in order and never tests anywhere at all whether two versions are really compatible —
compatibility is accepted as a promise attached to the version number itself, and it is never
separately verified. The selection algorithm gives the correct result in a world where this
promise is kept; when the promise is not kept (when the new version does not offer the same
interface as the old one), the algorithm still selects the same number, but whether the selected
version actually works is a separate question — the answer to that question is found not in the
module file, but in whether the source itself compiles.

This computation also runs, for `libraryD`, through a two-layer chain: `libraryD` receives one
request directly from the main module (`2.1.0`, written) and receives another request indirectly
through `libraryC` (`2.0.0`, derived). Selection makes no distinction at all between the two —
requests are compared not by where they come from but by which number they carry, and the
greater one (`2.1.0`) wins. Whether a request is written or derived is not an **input** to the
selection algorithm at all; it is only a separate observation, classifying the request's source,
that this lesson measures.

The importance of this distinction grows as the number of requests grows. In the measurement
above there are four modules and six requests; in a real dependency graph this number can be
measured in the hundreds, and the same module being requested from dozens of different paths is
an ordinary situation. The selection rule does not change at this scale either: for every
module, the greatest of the versions requested by all paths leading to that module is taken. The
algorithm itself is insensitive to the depth of the graph — whether the request reaching
`libraryD` comes from two steps away through `libraryC`, or directly from the main module, the
comparison consists of nothing more than placing the same two numbers side by side and picking
the larger one. The complexity sits in the size of the graph, not in the rule itself.

#### The Limiting Measurement: Not the Newest, the Greatest Written

The run above also carries one more catalog value: the "newest version available" for `libraryC`
is given as `1.9.0`, but the selected version is `1.6.0`. **PT11.** Selection never asks at all
about the newest version a module has available; it only looks at requests actually written (or
derived), and takes the greatest of those. This is the most misunderstood side of minimal
version selection: the rule does not **keep a dependency up to date**, it only satisfies whatever
has been requested somewhere in the source (directly or indirectly). `libraryC` may have a
`1.9.0` version, but as long as no request asks for it, it never enters the selected version at
all.

That this is a deliberate choice becomes visible when you look at what it actually guarantees:
it is wanted that a dependency graph's result be independent of who compiles that graph and when.
If selection were "always take the newest," the same source text could compile with a different
set of dependencies at different times — even though the source itself never changed at all.
Minimal version selection instead looks only at what is written in the source (even if
indirectly); the same set of requests selects the same set of versions no matter when or where it
is run. The cost is explicit: to benefit from a dependency's new version, **a line requesting**
that version has to be written somewhere; no request rises on its own.

This has one more consequence: two separate developers, running the same dependency graph at the
same time, with no knowledge of each other at all, still arrive at the same set of versions —
because selection looks at no outside state at all (the catalog's current contents, which
versions are considered "newest"), only at what is written in the source. Upgrading a dependency
therefore requires a separate, deliberate step: either the directly requested version is changed
in the source, or an intermediate dependency's own request somewhere in the chain is raised.
Either way, the upgrade happens by **writing** a new number somewhere; until it is written,
selection always returns to the same result, because the computation itself is deterministic.

#### Written, Derived, Selected

In a module file's real source, these three concepts fall on three separate lines. The versions
the main module directly requests sit explicitly written in the module file's own declaration
block. Modules carrying only a derived version also fall into the same block, but these lines
are marked "indirect" — they show modules never `import`ed anywhere in the source, only an input
to the selection computation. The selected version emerges from the union of these two sets,
through the computation above; because the module file carries these three separately, why a
module ends at that version — its own request or another dependency's request — remains
traceable backward from the source.

In a real module file, this declaration block can be read line by line: every line carries a
module name and the version selected for that module, and a short note is added at the end of a
line for a module not directly requested but only derived. This note itself is not a version
piece of information, it is a piece of **origin** information — it says that module was never
`import`ed at all in the main module's source, it only entered the selection computation through
another module. The `Written` field in the measurement above is exactly the counterpart of this
note: for `libraryA`, `libraryB`, `libraryD` the `Written` field is `true` (these are `import`ed
in the main module's own source), and `false` for `libraryC`'s two requests (neither is ever
mentioned at all in the main module's source). The declaration block itself is written **after**
the selection computation — first every request is gathered, minimal version selection is run,
then only the result (the selected version) falls into the block. The intermediate steps — which
request came from which module — never appear at all in the module file itself; this information
exists only for a moment while the selection computation is running, exactly as it sits in the
`requests` slice above.

These three distinctions (written, derived, selected) have a practical consequence: someone
reading a module file can tell apart, with two different questions, why a dependency stands
there. The answer to "did I request this module?" sits in the direct/indirect distinction. The
answer to "which version did we get?" sits on the selected version line — but this second
question never comes out of **any single** line of the source alone, because the selected version
is the result of comparing more than one request. The module file is therefore less a
declaration list and more the **final state of a computation**: every line carries the winner of
a comparison that does not appear in the source.

Reading where a dependency's version comes from off a declaration line (**Modules, Tooling and
the Ecosystem**) and determining which version a package is published under (**Python Projects:
Packaging and Testing**) were already built in other languages' own mechanisms; this lesson does
not repeat that model, it only measures Go's own selection rule — taking the greatest of the
requests.

#### Summary

- Requests in a dependency graph come from two sources: those written in the main module's own
  source, and those derived from a dependency's own module file.
- Minimal version selection takes the **greatest** of all requests written for a module; it
  carries the name "minimal" because it is the lowest common point that violates no request.
- Whether a request is written or derived does not affect the selection computation; only which
  number is carried is compared.
- The limiting measurement: the selected version is not the newest version in a module's
  catalog, only the greatest of the ones actually requested — no request rises on its own.
- This measurement was run without making a real network call at all, by modeling the
  dependency graph within the source.

#### Next Step

This lesson measured who selects a module's version: the choice is determined neither by a
person nor by a separate tool, it is determined by the requests themselves — whether written in
the source or derived from a dependency, the computation applies the same rule to both. A
module's version therefore sits outside the source, as an output of the selection algorithm. But
for the **form** of the source inside a module and the **convention** it follows, the answer to
the same question sits in a different place entirely: not a person or a rulebook applies them,
but Go's own command family. The next lesson calls these commands from within the program and
measures how an unwritten formatting rule changes the source, and which tool catches a pattern
the compiler does not reject.

### Go Command Family

Source: https://academia.sh/en/courses/go-fundamentals/go-command-family

The language's own command family enforces two unwritten rules, called from within the program. When run through a formatter, a source is compared byte for byte, and 7 of an 18-line source's lines are counted as changed; the vet tool catches a pattern the compiler does not reject (a mismatch between the type a print call's format verb expects and the type of the value given). The limiting measurement shows that two sources, one formatted and one not, give the same output when run — the form changes, the meaning does not.

The previous three lessons called three members of the language's own command family: the
compiler decided whether a source was valid, the formatter canonicalized its appearance, vet
caught a pattern the compiler overlooked. All three were concerned with the source **itself**.
This final lesson looks at the family's fourth member: the doc tool that produces the text
describing what a package offers to the outside. This tool neither compiles the source, nor
formats it, nor tests it against a pattern — it only **reads**, and decides which name, with
which comment, to show and when. The course's final question: is it the comment itself that
makes this decision, or the first letter of the name?

Across the course's first three topics, only one member of this command family was called: the
compiler. Its job was narrow — deciding whether the source turns into an executable program. But
beyond whether a source text compiles or not, two more questions stand: how does this source
**look**, and which **conventions** does this source follow? The compiler is indifferent to
both — a source produces the same program no matter what its indentation, spacing, or line
breaks are, and the language's syntax rules never decide at all whether a convention is "good" or
"bad"; they only look at whether the convention is valid. The answer to these two questions
therefore sits outside the compiler, in two separate members of the same command family.

#### Running a Source Through the Formatter

**PT12.** A source's form — indentation, spacing, alignment — is not enforced by the language's
syntax rules at all; a source carrying the same meaning can be written in many different forms.
**PT13.** Even so, the formatter turns any valid source given to it into a single canonical form,
and this transformation never changes the source's **meaning** at all.

The routines below use the exact same compiler-calling pattern built in the previous lessons: the
source is written to a temporary module, the command is called, the result is read. The
difference is here — this time the command called does not **compile** the source, it
**changes** the source itself. So the measurement, too, reads something different: in the
previous lessons there was a single question (did it compile or not); here two sources — before
and after formatting — are held side by side and compared. Every measurement again runs against
its own completely empty temporary module and is called with the `GOTOOLCHAIN=local` environment
variable, exactly as in the previous two lessons — so that the command called neither sends a
network request nor sees a file left behind by a previous trial.

```go
// tools/tools.go — shared routines that call the go command family from within the program
package tools

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

func newModule(dir string) {
	os.MkdirAll(dir, 0o755)
	os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module trial\n\ngo 1.24\n"), 0o644)
}

func environment() []string {
	return append(os.Environ(), "GOTOOLCHAIN=local")
}

// Format runs source through the formatter in a temporary module; it returns the
// source before and after formatting, as well as how many lines changed.
func Format(source string) (before, after string, changedLines int) {
	dir, _ := os.MkdirTemp("", "trial")
	defer os.RemoveAll(dir)
	newModule(dir)
	path := filepath.Join(dir, "main.go")
	os.WriteFile(path, []byte(source), 0o644)

	cmd := exec.Command("go", "fmt", "./...")
	cmd.Dir = dir
	cmd.Env = environment()
	cmd.Run()

	data, _ := os.ReadFile(path)
	before = source
	after = string(data)

	beforeLines := strings.Split(strings.TrimRight(before, "\n"), "\n")
	afterLines := strings.Split(strings.TrimRight(after, "\n"), "\n")
	if len(beforeLines) == len(afterLines) {
		for i := range beforeLines {
			if beforeLines[i] != afterLines[i] {
				changedLines++
			}
		}
	}
	return before, after, changedLines
}

// RunAndCapture runs source in a temporary module and returns its standard output.
func RunAndCapture(source string) string {
	dir, _ := os.MkdirTemp("", "trial")
	defer os.RemoveAll(dir)
	newModule(dir)
	os.WriteFile(filepath.Join(dir, "main.go"), []byte(source), 0o644)

	cmd := exec.Command("go", "run", ".")
	cmd.Dir = dir
	cmd.Env = environment()
	output, _ := cmd.Output()
	return strings.TrimRight(string(output), "\n")
}

// VetCatches compiles source in a temporary module and runs it through vet; it is
// used to catch patterns the compiler lets through but vet rejects.
func VetCatches(source string) (compiled, vetCaught bool) {
	dir, _ := os.MkdirTemp("", "trial")
	defer os.RemoveAll(dir)
	newModule(dir)
	os.WriteFile(filepath.Join(dir, "main.go"), []byte(source), 0o644)

	build := exec.Command("go", "build", "-o", os.DevNull, "./...")
	build.Dir = dir
	build.Env = environment()
	compiled = build.Run() == nil

	vet := exec.Command("go", "vet", "./...")
	vet.Dir = dir
	vet.Env = environment()
	vetCaught = vet.Run() != nil
	return compiled, vetCaught
}
```

`Format` writes the source to a file and calls the formatter, then reads the same file again —
because formatting changes the source **in place**, before and after are two separate readings.
If the line counts are equal (the formatter neither added nor removed a line), it compares the
corresponding lines one by one and counts how many changed. `VetCatches`, on the other hand,
calls two separate commands one after another: first the compiler, then vet — only the
success/failure state of each is read, no output text is stored or printed at all.

#### One Source, One Convention

The measurement below gathers two separate questions into a single run: when the same untidy
source is run through the formatter, how much of it changes, and when a separate source is run
through vet, is a pattern the compiler overlooked caught? The two measurements are independent of
each other — one tests the appearance of one source, the other tests the validity of a different
source — but both are carried out with the same procedure, calling the command from within the
program and only reading the result.

```go
// main.go — the formatter changes the source, vet catches a pattern the compiler does not reject
package main

import (
	"fmt"
	"strings"

	"lesson/tools"
)

func main() {
	source := "package main\n\n" +
		"import \"fmt\"\n\n" +
		"type Point struct {\n" +
		"  X int\n" +
		"  Y int\n" +
		"  Label string\n" +
		"}\n\n" +
		"func Add(a int,b int) int {\n" +
		"      return a+b\n" +
		"}\n\n" +
		"func main() {\n" +
		"    p := Point{X: 1, Y: 2, Label: \"a\"}\n" +
		"    fmt.Println(p,Add(2,3))\n" +
		"}\n"

	before, after, changedLines := tools.Format(source)
	totalLines := strings.Count(strings.TrimRight(before, "\n"), "\n") + 1

	fmt.Println("-- does the formatter change the source --")
	fmt.Printf("identical byte for byte: %v\n", before == after)
	fmt.Printf("total lines: %d, changed lines: %d\n", totalLines, changedLines)

	fmt.Println()
	fmt.Println("-- limiting measurement: the form changes, the meaning does not --")
	outputBefore := tools.RunAndCapture(before)
	outputAfter := tools.RunAndCapture(after)
	fmt.Printf("output of the source before formatting: %s\n", outputBefore)
	fmt.Printf("output of the source after formatting: %s\n", outputAfter)
	fmt.Printf("are the two outputs the same: %v\n", outputBefore == outputAfter)

	fmt.Println()
	fmt.Println("-- vet catches a pattern the compiler does not reject --")
	patternSource := "package main\n\n" +
		"import \"fmt\"\n\n" +
		"func main() {\n" +
		"\tfmt.Printf(\"%d\\n\", \"text\")\n" +
		"}\n"
	compiled, caught := tools.VetCatches(patternSource)
	fmt.Printf("compiled: %v\n", compiled)
	fmt.Printf("did vet catch it: %v\n", caught)
}
```

```
-- does the formatter change the source --
identical byte for byte: false
total lines: 18, changed lines: 7

-- limiting measurement: the form changes, the meaning does not --
output of the source before formatting: {1 2 a} 5
output of the source after formatting: {1 2 a} 5
are the two outputs the same: true

-- vet catches a pattern the compiler does not reject --
compiled: true
did vet catch it: true
```

**PT14.** 7 of the 18-line source's lines change after passing through the formatter; the
remaining 11 lines were already in canonical form and are never touched at all. **PT15.** In six
of the seven changed lines, the indentation character turns from a space into a tab; in two of
these, extra spacing is also added so that the struct fields' types line up in the same column,
in two the space after a comma is completed — one in a function definition's parameter list, one
in a call — and in one, a space enters on both sides of an addition operator — these categories
overlap on some lines (an indentation fix and an alignment can occur together on the same line),
but none of them adds a new line or deletes a line.

What is unwritten here is a **preference**: the person writing the source did not write whether
to use spaces or tabs for indentation, whether to align a struct's fields, or whether to leave a
space after a comma. The formatter fills these gaps with its own canonical decision — and this
is another appearance of the course's "defined" class from the zero-value discussion: something
unwritten is filled not by the compiler's rejection, but by a separate tool's own rule.

This decision standing at a single canonical form produces a separate consequence in situations
where more than one person works on the same source. When indentation and alignment stay a
matter of preference, two sources carrying the same meaning can diverge from each other by
appearance alone — and this difference, when compared against the source's previous state (when
looking at what changed line by line), shows up as noise: which line changed **in meaning** and
which changed only **in form** get mixed together. The formatter's canonical decision cuts this
noise at the source — no matter which indentation the writer chooses, the source always turns
into the same form once it passes through the formatter, and this is why the real difference
between two sources can only be seen by comparing the two formatted states.

#### Vet Catches a Pattern the Compiler Lets Through

**PT16.** The call `fmt.Printf("%d\n", "text")` compiles — because `Printf`'s variadic
arguments are accepted as type `interface{}`, passing a string as the counterpart of a `%d` verb
is syntactically valid in the compiler's eyes. **PT17.** The same source is rejected when run
through vet: `%d` expects an integer while the argument is a string, and this mismatch between
the format verb and the value's type is not a rule the compiler checks.

This distinction expands the course's "rejected" class. In the previous topics the rejecting
party was always the compiler; here the rejecting party is **vet** — a separate command, a
separate pass, a separate rule set. The compiler only looks at whether the source turns into a
program the virtual machine can run; the agreement between `Printf`'s format string and its
arguments falls outside this question, because in the compiler's eyes the format string is an
ordinary string value with no special meaning at all. Vet **parses** this string and matches
every verb against its corresponding argument, catching the ones that do not match — it works at
a layer the compiler never looks at all.

The value of this catch is in changing where the error surfaces. A call given a string in place
of a `%d` compiles and runs — but when it runs, instead of the expected number it prints the
format verb itself along with an error marker, and this is only visible once that call line
actually runs. If such a call in a codebase falls onto a rarely run branch, the defect can go
unnoticed for a long time; and because compilation raises no warning at all, someone reading the
source cannot infer this from the source itself. Vet catches this call **without ever running it
at all**, purely by reading the source — a tool that moves a defect that would otherwise fall
into the course's "left open" class, without ever leaving it to run time, into the "rejected"
class instead. The compiler itself does not perform this move, because the compiler's job is
limited to checking syntax and type agreement; the content of a format string falls outside the
compiler's type system, it is a string value interpreted only at run time.

#### The Limiting Measurement: The Form Changes, the Meaning Does Not

**PT18.** The same source's state before and after formatting, when each is run separately,
gives exactly the same output: `{1 2 a} 5`. The byte-for-byte comparison says `false`, while the
run-output comparison says `true` — these two results do not contradict each other, on the
contrary they build the lesson's thesis: the formatter changes the source's **appearance**, not
its **behavior**.

The reason for this lies in what the formatter does: indentation, spacing, and alignment —
everything the formatter touches — are whitespace that is **meaningless** in the language's
syntax. An `int` variable's name, the order of statements in a function's body, a struct's field
names — none of these change during formatting, because the formatter never touches them at all.
The boundary here is sharp: the formatter reads a **parse tree** and rewrites the same tree with
canonical whitespace, it never changes the tree itself. This is why no new assumption is needed
at all about a formatted source's behavior — what the measured program does when it runs is the
same after formatting as it was before formatting.

This boundary sits in a different place than vet's boundary. The formatter never rejects a
source at all — it accepts and rewrites every valid source given to it, and never decides at all
whether the input is "good" or "bad." Vet does the exact opposite: it never changes the source at
all, it only rejects or passes it. Both tools are members of the same command family, both work
at a layer the compiler does not look at, but one does its job by transforming the source, the
other by judging the source. This lesson tested both with the same procedure — writing the
source to a temporary module and calling the command — and both results were read only as a
single flag (did it change, did it pass); at no stage was an error text or a formatted diff
printed line by line at all.

#### Summary

- The language's own command family covers not only compiling but also formatting and vetting;
  all three, as in this lesson, can be called from within the program as an argument.
- The formatter turns the source given to it into a single canonical form; 7 of an 18-line
  source's lines change in this transformation, the rest were already in canonical form.
- Vet catches a pattern the compiler does not reject (a mismatch between a format verb and an
  argument's type); the compiler never checks this agreement at all, because in its eyes the
  format string is an ordinary string value.
- The limiting measurement: the source before and after formatting gives exactly the same
  output when run — the form changes, the meaning does not.
- Both tools' names appeared only in the code block, as the argument of a call; in the prose
  they were referred to only by their job names.

#### Next Step

This lesson measured who applies a source's form and a pattern the compiler overlooks — both are
separate members of the language's own command family, both work at a layer the compiler does
not look at. But none of these three tools (the compiler, the formatter, vet) ever reads at all
the text that tells what a package offers to the outside — the writing that explains what a name
is for. The course's final lesson looks at this last corner: how is a document produced from the
source, which names never enter that document at all, and does the doc tool check a comment's
**correctness** — or only its **position**?

### Documentation Conventions

Source: https://academia.sh/en/courses/go-fundamentals/documentation-conventions

Six names — three exported, three unexported; in each triple, one has a comment adjacent to it, one has a comment separated by a blank line, one has no comment at all — are asked of the doc tool. The number of names that show up depends only on the case of the letter (3/6), while the number that carry a description depends only on the comment's position (1/6). The limiting measurement shows that an incorrect doc comment is displayed exactly the same way: the doc tool never checks the comment's correctness at all.

The previous three lessons called three members of the language's own command family: the
compiler decided whether a source was valid, the formatter canonicalized its appearance, vet
caught a pattern the compiler overlooked. All three were concerned with the source **itself**.
This final lesson looks at the family's fourth member: the doc tool that produces the text
describing what a package offers to the outside. This tool neither compiles the source, nor
formats it, nor tests it against a pattern — it only **reads**, and decides which name, with
which comment, to show and when. The course's final question: is it the comment itself that
makes this decision, or the first letter of the name?

#### Adjacent Comment, Detached Comment

**PT19.** When a comment is written **adjacent** to a definition (with no blank line in
between), that comment counts as that definition's doc comment; when a blank line comes in
between, the same comment text is no longer attached to any definition at all. **PT20.** The doc
tool lists only exported names; no matter how carefully an unexported name's doc comment is
written, that name never enters the documentation at all.

```go
// tools/tools.go — shared routines that call the doc tool from within the program
package tools

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

func environment() []string {
	return append(os.Environ(), "GOTOOLCHAIN=local")
}

// SetUpPackage writes the given package source to the "trial/pkg" subpackage and
// returns the module's root directory; the caller runs the doc tool in this directory.
func SetUpPackage(pkgSource string) string {
	dir, _ := os.MkdirTemp("", "trial")
	os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module trial\n\ngo 1.24\n"), 0o644)
	os.MkdirAll(filepath.Join(dir, "pkg"), 0o755)
	os.WriteFile(filepath.Join(dir, "pkg", "pkg.go"), []byte(pkgSource), 0o644)
	return dir
}

// VisibleInDocs asks the doc tool about a name; visible is true if the name appears in
// the docs, and hasDescription says whether there is a comment text below the line.
func VisibleInDocs(dir, name string) (visible, hasDescription bool) {
	cmd := exec.Command("go", "doc", "./pkg."+name)
	cmd.Dir = dir
	cmd.Env = environment()
	output, err := cmd.Output()
	if err != nil {
		return false, false
	}
	lines := strings.Split(strings.TrimRight(string(output), "\n"), "\n")
	nonEmpty := 0
	for _, l := range lines {
		if strings.TrimSpace(l) != "" {
			nonEmpty++
		}
	}
	// When the output is only the package heading and the signature line, it carries
	// two non-empty lines; when a description is added, this count increases.
	return true, nonEmpty > 2
}

// DocText returns the full output the doc tool gives for a name.
func DocText(dir, name string) string {
	cmd := exec.Command("go", "doc", "./pkg."+name)
	cmd.Dir = dir
	cmd.Env = environment()
	output, _ := cmd.Output()
	return strings.TrimSpace(string(output))
}
```

`VisibleInDocs` calls the doc tool directly with a name: if the name is found, the output
carries at least the package heading and the signature line; if not found, the tool returns with
a non-zero exit code, and the error text is never read at all. Whether there is a description is
counted by looking at how many lines the output carries — when a description is added below the
signature line, the output gains a third non-empty line.

#### Six Names, Two Independent Questions

```go
// main.go — the doc is produced from the source; visibility depends on the letter, description on position
package main

import (
	"fmt"
	"os"

	"lesson/tools"
)

func main() {
	pkgSource := "// Package pkg is a small library for doc comment experiments.\n" +
		"package pkg\n\n" +
		"// LargeCommented returns the sum of the two given numbers.\n" +
		"func LargeCommented(a, b int) int { return a + b }\n\n" +
		"// smallCommented returns the sum of the two given numbers.\n" +
		"func smallCommented(a, b int) int { return a + b }\n\n" +
		"func LargeUncommented(a, b int) int { return a - b }\n\n" +
		"func smallUncommented(a, b int) int { return a - b }\n\n" +
		"// LargeDetachedComment is a description, but it is separated from the definition by a blank line.\n\n" +
		"func LargeDetachedComment(a, b int) int { return a * b }\n\n" +
		"// smallDetachedComment is a description, but it is separated from the definition by a blank line.\n\n" +
		"func smallDetachedComment(a, b int) int { return a * b }\n\n" +
		"// Average returns the average of the given numbers.\n" +
		"func Average(numbers ...int) int {\n" +
		"\tsum := 0\n" +
		"\tfor _, n := range numbers {\n" +
		"\t\tsum += n\n" +
		"\t}\n" +
		"\treturn sum\n" +
		"}\n"

	dir := tools.SetUpPackage(pkgSource)
	defer os.RemoveAll(dir)

	names := []string{
		"LargeCommented", "smallCommented",
		"LargeUncommented", "smallUncommented",
		"LargeDetachedComment", "smallDetachedComment",
	}

	fmt.Println("-- six names, visibility and description in the docs --")
	visibleCount, describedCount := 0, 0
	for _, name := range names {
		visible, hasDescription := tools.VisibleInDocs(dir, name)
		if visible {
			visibleCount++
		}
		if hasDescription {
			describedCount++
		}
		fmt.Printf("%-22s visible=%-5v description=%v\n", name, visible, hasDescription)
	}
	fmt.Printf("total: %d/6 visible, %d/6 described\n", visibleCount, describedCount)

	fmt.Println()
	fmt.Println("-- limiting measurement: the doc tool does not verify the comment's correctness --")
	fmt.Println(tools.DocText(dir, "Average"))
	fmt.Printf("Average(2, 4, 6) real result: %d\n", actualAverage(2, 4, 6))
}

// actualAverage independently shows what the produced Average function actually does:
// the comment says "average," the function returns the sum.
func actualAverage(numbers ...int) int {
	sum := 0
	for _, n := range numbers {
		sum += n
	}
	return sum
}
```

```
-- six names, visibility and description in the docs --
LargeCommented         visible=true  description=true
smallCommented         visible=false description=false
LargeUncommented       visible=true  description=false
smallUncommented       visible=false description=false
LargeDetachedComment   visible=true  description=false
smallDetachedComment   visible=false description=false
total: 3/6 visible, 1/6 described

-- limiting measurement: the doc tool does not verify the comment's correctness --
package pkg // import "trial/pkg"

func Average(numbers ...int) int
    Average returns the average of the given numbers.
Average(2, 4, 6) real result: 12
```

**PT21.** Three of the six names show up in the docs, three do not show up at all; all three that
show up start with an uppercase letter, all three that do not show up start with a lowercase
letter. **PT22.** Of the three visible names, only one carries a description — `LargeCommented`,
whose comment is adjacent to the definition; `LargeUncommented`, which has no comment at all, and
`LargeDetachedComment`, whose comment is separated by a blank line, both show up by name in the
docs but without a description.

These two observations answer two completely independent questions. The answer to "does this
name exist in the docs?" looks only at the case of the letter — `smallDetachedComment`'s comment
is detached in exactly the same way as `LargeDetachedComment`'s, but the two names' fates in the
docs are not the same at all, because the difference is not in the comment, it is in the name
itself. The answer to "does this name have a description?" looks only at the comment's position —
`LargeUncommented` has no comment at all; `LargeDetachedComment` has a comment, but it is
disconnected from the definition; both show up in the docs without a description, because
neither has an adjacent comment for the doc tool to read.

#### The Limiting Measurement: The Doc Tool Does Not Check Correctness

**PT23.** The `Average` function's doc comment says "returns the average of the given numbers";
the function itself returns the sum. When `Average(2, 4, 6)` is called, the value returned is
`12` — not the average, the sum. **PT24.** The doc tool prints this comment exactly as it is,
never testing its correctness at all; neither the compiler, nor vet, nor the doc tool catches this
mismatch.

This is the course's final "left open" line, and it stands in a different place than the earlier
ones. The earlier "left open" examples (writing to an uninitialized map stopping with a panic, a
string being indexed by byte) were always a result that surfaced **at run time** — something
happened when you ran the program. What is left open here has nothing to do with run time at
all: the `Average` function runs without error, does not panic, does not return the wrong type —
only **what its name says and what it does do not agree**, and there is no tool at all to check
this mismatch. Just as the compiler does not check a string's content, it does not check a
comment's content either; vet matches format verbs against argument types but never compares
what a comment describes against what the code does; the doc tool does not even connect these two
files (the source and the comment) to each other, it only copies the comment according to its
position and displays it. What is unwritten here is a **verification mechanism**, and the
language puts nothing at all in its place.

#### Course Wrap-Up

Go Fundamentals opened with a single question: when writing a program, the thing that matters
most is not what is written, it is what is unwritten. Eighteen lessons asked this question across
three mechanisms: in the language itself (zero values, slice and map behavior), in the compiler
(patterns rejected, from an unused variable all the way to the export rule), and outside the
source (version selection, form, documentation).

The first claim was confirmed: in Go there is no such state as "uninitialized" — the place of an
unwritten value is always filled by a defined counterpart, whether that is a zero value or a
slice bound's default. The second claim showed that every pattern the compiler rejects is a
decision to "narrow the counterpart of the unwritten" — from an unused variable to an unexported
field, the compiler does not accept some kinds of unwrittenness at all. The third claim
established that definedness **works in the reading direction and stops in the writing and
interpreting direction**: this final lesson showed the most extreme example of this — reading a
comment is defined, but whether that comment **is correct** is checked nowhere at all.

The course closes without overgeneralizing: these three classes (defined, rejected, left open) do
not cover all of Go's behavior, they only classify the answers given to the single question these
eighteen lessons asked — what happens when you do not write this?

| Lesson | Unwrittenness measured | Counterpart (defined · rejected · left open) | Limiting measurement |
|---|---|---|---|
| Go's Design Goals | Four reasons for rejection: an unused variable/import, an unwritten return, an implicit conversion | rejected | Rejecting an implicit conversion does not shorten the source: the uncompiled line is 15, its compiled counterpart is 22 characters |
| Environment Setup and the First Program | Three parts mandatory to run: the package declaration, `func main`, the module file | rejected | The module file sits outside the source; with the source completely unchanged, removing only the module file stops compilation |
| Variables and Constants | Of six constants, only two have their value written in the source, four are produced with `iota` | defined (`iota`) · rejected (a short declaration at package level) | A short declaration is valid only inside a function; the same line is rejected at package level |
| Basic Types | An untyped constant's default type is `int`; a sum exceeding `int8`'s limit wraps to -128 | defined (default type, overflow) · left open (floating-point comparison) | Rejecting an implicit conversion does not apply to constants; an untyped constant fits the target type on its own |
| Zero Values | The distribution of fifteen unwritten cases — the course's binding count is produced here | 8 defined · 4 rejected · 3 left open | `nil` can be appended to a slice, `nil` cannot be written to a map; both are a zero value in the same way |
| Control Flow | The optionality of `for`'s three parts, an expressionless `switch`, an unwritten `break` | defined (all five of the five forms) | Reversing the default is not free: wanting to fall through requires writing `fallthrough` |
| Functions | A named return never written in the body at all; a returned value never captured at all | defined (zero return) · rejected (an unwritten return) · left open (an ignored return) | Once a returned value is bound to a name, the unused-variable rejection comes into play |
| Variadic Functions | A parameter given no arguments at all being a `nil` slice | defined | A `nil` slice and an empty slice are the same in terms of `len`, separate in operations that look at identity |
| Arrays and Slices | The array/slice difference in an assignment line: a copy for an array, sharing for a slice | defined | When `append` exceeds capacity, sharing is cut off; the copied header now points to a separate backing array |
| Slice Operations | The default for unwritten slicing bounds | defined | The same `append` call, with the same source text, gives two different results at two different capacities |
| Maps | The class of four access forms: an existing/non-existing key, reading from/writing to a `nil` map | defined · left open (writing panics) | Without comma-ok access, an entry whose value is zero cannot be told apart from one that does not exist at all |
| Structs | The zero value of unwritten fields; embedding carrying unwritten names to the outer type | defined | Embedding is not inheritance: when an embedded type's method is called, the receiver is the embedded value itself, not the outer struct |
| Pointers | An array and a struct passed without taking their address not changing on the caller's side | defined (copy) · left open (reading a field from a `nil` pointer) | A pointer is a value too, and it gets copied; reassigning it changes only the local copy |
| Strings and Runes | `len` counts bytes, indexing gives a byte, `range` gives a rune | defined (a promise of bytes) · left open (no counterpart for "character") | A rune count is not a character count either; combining code points break this |
| Package Layout and Visibility | Exporting depending on a single letter | rejected (from outside the package) · defined (inside the package) | The boundary is the package boundary, not the file boundary; two files of the same package share a lowercase name with no `import` at all |
| Modules and Dependencies | An unwritten dependency version | defined (minimal version selection) | The selected version is not the catalog's newest, it is the greatest of the requests actually written (or derived) |
| Go Command Family | An unwritten source form; a pattern the compiler does not reject | defined (form) · rejected (vet) | The form changes (7 of 18 lines), the meaning does not: the source's output is the same before and after formatting |
| Documentation Conventions | The name and description entering the docs | defined (visibility by letter) · left open (content is not checked) | The doc tool does not check a comment's correctness; a wrong description is printed exactly the same way |

#### Summary

- The doc tool lists only exported names; whether a name shows up in the docs is determined
  only by whether its first letter is uppercase, not by the comment itself.
- Whether a comment counts as a description depends on its being written **adjacent** to a
  definition; a single blank line in between detaches the comment from that definition and
  keeps it out of the docs.
- Three of six names show up in the docs (only the uppercase ones), and only one of the visible
  ones carries a description (only the one with an adjacent comment) — the two questions are
  independent of each other.
- The limiting measurement: the doc tool never checks at all whether a comment is correct; a
  wrong description is printed exactly the same way as a correct one.
- The course closed with three classes (defined, rejected, left open); these classes do not
  cover all of Go's behavior, only the single question these eighteen lessons asked.

