52. What's really behind a slice, a map, and a string
52.1 Slice — covered fully in Chapter 6
A three-word header: pointer, length, capacity. Worth repeating here because it's the single most consequential piece of "what's really there" knowledge in the whole language.
52.2 Map — a hash table of buckets, with a twist
Go's map is implemented as an array of buckets, each holding a small
fixed number of key/value pairs plus overflow bucket pointers for
collisions, with the array growing (and old buckets migrating
incrementally, not all at once) as the map fills. The "twist": Go
deliberately randomizes the starting bucket for iteration precisely to
prevent programs from ever depending on a stable order (Chapter 7.3) —
this is enforced by the implementation, not just documented as
unspecified.
52.3 String — a two-word, read-only view
type stringHeader struct {
ptr *byte // pointer to an immutable byte array
len int
}
No capacity field, because strings never grow in place — every operation
that "modifies" a string (concatenation, slicing rules aside) allocates a
new one. Slicing a string (s[2:5]) does share the underlying byte
array without copying, exactly like slicing a slice (Chapter 6.2) — one
more reason large substrings of a huge string you no longer need the rest
of can pin that whole backing array in memory unless you explicitly copy
the substring out.
52.4 Real-world example
A service parsing huge log lines and storing only a short substring
(say, a request ID) per entry can accidentally retain the entire original
line's byte array in memory for every stored substring, because slicing
a string doesn't copy. The fix mirrors Chapter 6's slice-aliasing fix:
strings.Clone(sub) (Go 1.18+) forces an actual copy, letting the
original large string be garbage collected.