Even trains do not demand a wider track.

The railway gauge is the Gutenberg layer. It has been 1435mm — standard gauge — since George Stephenson in 1830. Not because 1435mm is the optimal width for a railway. Because it was the width already there when everyone else started building. The first mover’s guess became the standard. The standard became the infrastructure. The infrastructure became load-bearing.

Every train built since 1830 that wants to run on standard gauge track conforms to 1435mm. Not the other way around. The track does not widen for the new train. The new train is built to fit the track.

The framework that demands a wider track with every release has made Brunel’s mistake. The engineering is correct. The economics are ruinous for everyone downstream.


Library Versus Framework

A library is a book on the Billy. You take it off the shelf when you need it. You put it back. The rest of the books do not move. The Billy does not change because you added a new book. Libraries have narrow seams — a function signature, a struct layout, a documented interface. You call the library. The library does not call you.

A framework is the Billy deciding how to organise your entire house. You do not call the framework. The framework calls you — it controls the main loop, the lifecycle, the dependency injection, the configuration format, the logging system, the test runner. The framework owns the Gutenberg layer and makes you implement the Semantic layer according to its opinions. Inversion of Control. Your code is a plugin in someone else’s opinionated runtime.

When the library changes, your call sites change. Manageable — the call sites are finite, the interface is narrow, the compiler tells you exactly where.

When the framework changes, your whole codebase changes. The framework wired everything together. The new version wired it differently. Every class that implemented the framework’s interfaces, every configuration file that used the framework’s conventions, every test that used the framework’s test runner — all of it potentially broken.

I have a new server. Update the internet. That is the framework release cycle, stated honestly.


OOP: Objects All the Way Down

The framework culture is not an accident. It grows directly from OOP’s fundamental architectural choice: objects all the way down.

The OOP promise was that objects model reality. The problem is that reality has layers that move at different speeds, and objects flatten them into one. The brick IS the wall IS the room. There is no envelope versus letter. No LPN versus VIN. No pages and bytestream. No rooms versus walls versus bricks. Just objects — Semantic post-its with Def meaning fused to Gutenberg structure — turtles all the way down.

The is-a collapse:

A room IS a collection of walls. The room’s identity is derived from its components. Change the wall class and the room class breaks. Refactor the building hierarchy and everything that extends it recompiles. The Semantic identity (a room, a space with purpose) is fused to the Gutenberg composition (walls, floors, a ceiling).

The has-a relationship is the correct model: the room HAS walls. The walls are replaceable. The room’s meaning survives the replacement. Better bricks, same room. Moore’s Law improves the bricks. The room benefits for free.

OOP chose is-a. The entire class hierarchy is a chain of is-a relationships. The new compiler arrives: recompile the whole iceberg. The vtable layout changed. The name mangling changed. The ABI assumptions shifted. Every object that IS something in the hierarchy pays the migration cost.

C++, Java, .NET as the same mistake at different layers:

  • C++: vtables, name mangling, exception tables. Semantic post-its embedded in the Gutenberg ABI. New compiler version: maybe the vtable layout changed. Relink everything.
  • Java: runtime class identity, the JVM as a Semantic layer fused to the execution layer. New JVM: retest everything because GC behaviour changed.
  • .NET: the same as Java with Microsoft’s opinions. New runtime: the whole iceberg migrates together or stays behind.

No framework in these ecosystems can have a stable seam because the object model is not designed to be stable. It is designed to be correct — and correctness in OOP means the whole hierarchy is coherent, which means changing one thing requires changing everything related.

The Linux kernel’s answer: no C++ in the kernel. The struct is bytes at offsets. The function pointer is a documented address. The ABI is a published table that the compiler must produce and every driver must conform to. No opinions in the substrate.


Brunel Was Right. The Economics Were Wrong.

In 1838 Isambard Kingdom Brunel built the Great Western Railway on 7-foot gauge (2140mm). Broader track, smoother ride, faster trains. Technically superior to standard gauge in almost every measurable way.

In 1892 the British government forced conversion of the entire Great Western network to standard gauge. 1600 miles of track. Every locomotive. Every carriage. Every station platform. All changed in a single weekend — 4,200 men working simultaneously across the network.

The cost was not Brunel’s gauge being wrong. The cost was that the infrastructure had been load-bearing for fifty years. Everything built on it had to move simultaneously. Brunel was right about the engineering. He was wrong about the economics of changing the thing everything else depends on.

Infra changes are very expensive — not because change is bad, but because infrastructure is what everything else is built on. The migration cost is not the infrastructure change. The migration cost is every system that depended on the infrastructure being where it was.

Spring Boot 3 requires Java 17. The track widened. Every application on Java 11 must be rebuilt before it can use the new track. The application did not ask for a wider track. The framework decided.

Angular 2 was not a migration from AngularJS. It was a new track. The AngularJS trains do not fit. Rebuild from scratch.

The Windows UTF-16 decision from the 1990s is still being paid for in 2026. Every language runtime on Windows must either speak UTF-16 natively or maintain a translation layer at every system call boundary. The infrastructure decision made thirty years ago costs money today, every day, in every Python script, every Go program, every Ruby gem that touches a Windows filename.


UTF-16: We Need a Wider Byte. UTF-8: No We Don’t.

The most instructive track-widening failure in computing history is UTF-16.

Unicode needed more than 256 code points. The obvious solution: widen the track to 16 bits. Every character is now 2 bytes. Old 8-bit trains do not fit. And when 65,536 code points turned out to not be enough — because human writing contains more — surrogate pairs arrived. Some characters need 4 bytes. The track widened again but not uniformly.

The BOM (Byte Order Mark) at the start of every UTF-16 file is the manifest you must read before the train departs. Big-endian or little-endian? Go back to page 1 to understand page 47. The old trains needed rebuilding. The new trains still need a station master to read the manifest.

The wchar_t in C/C++ — and Java’s char, and .NET’s char — made the same is-a collapse. The character IS a 16-bit integer. Not HAS a representation. IS one. When Unicode expanded past 65,536 code points the is-a fusion broke. Two 16-bit values that are not characters but together represent one character. strlen() on a Java string containing emoji gives the wrong answer — not because Java is broken, but because char IS a 16-bit integer and an emoji is not.

A 16-bit wchar_t architect looked at a character and said: a character IS a fixed-width integer. The house IS a collection of bricks. We will make enough bricks.

UTF-8 stayed on the same track:

Pike and Thompson looked at the same problem and said: the track is fine. The track is bytes. It has always been bytes. Put the routing information in the freight.

The insight: use the high bits of each byte to signal structure. 0xxxxxxx — plain ASCII, one byte, unchanged from 1967. 11xxxxxx — start of a multi-byte sequence. 10xxxxxx — continuation byte. No BOM. No wider byte. No station master.

Land anywhere in the stream and scan at most 3 bytes forward to find the next character boundary. Self-synchronising. O(1) boundary detection. The freight tells you how to read it. The track never moved.

The last page may be smaller:

Every page in UTF-8 is the same width — a byte. Except the last sequence, which may be a partial character if the stream is truncated. The infrastructure handles it. The character does not know or care. This is the correct Gutenberg model: uniform pages, variable content, the last page possibly smaller. No fixed-width assumption. No ceiling drawn at 65,536.

For the entire English-language internet — every ASCII-compatible system ever built — UTF-8 has zero overhead. The old trains run unchanged. They cannot even tell the track is now international.


The Evolvable Library

The correct model is not “never improve.” It is “improve without moving the track.”

SQLite has not broken its C API since 2000. The function signatures are stable. New functions arrive alongside old ones — never replacing them. Every language that wraps SQLite uses the same API. The Billy holds every book. The track fits every train.

The C standard library has been stable since the 1970s. fopen, fread, fwrite, fclose — same signatures, same behaviour. New libc implementations arrive (musl, glibc, bionic) and your code recompiles unchanged because the track did not move.

Git object format has been stable since 2005. New object types arrived without changing existing ones. Every git client ever written still reads the same objects. The track is the SHA. The SHA has not moved.

The Linux kernel ABI — no framework, no inheritance hierarchy, no C++ opinions. Drivers implement a struct of function pointers. The struct is the published track. New subsystems add new structs. Old structs keep working. Dual-track. No forced migration.

Each one: the infrastructure improved below the waterline. Everything above it ran unchanged. The cost was paid once by the infrastructure authors and zero times by everyone who built on it.


The Billy Holds Every Book

The framework that demands a wider track is the Billy that decided it only holds hardbacks now. The paperbacks must be rebound. The DVDs must find another shelf.

The lib that adds a new function without changing existing ones is the Billy that got a new shelf — more capacity, same dimensions, every existing book still fits.

The C ABI is 1435mm. The UTF-8 byte is 8 bits. The git SHA is 40 hex characters. The SQLite function signature is what the documentation says it is.

None of them moved the track when they improved. None of them demanded a wider byte. None of them asked the internet to recompile.

The train conforms to the track. The book fits the Billy. The freight announces its own width in the first byte. The last page may be smaller.

Infra changes are very expensive. The correct response is to improve the infrastructure in ways that do not move the track.


This post is part of the Gutenberg/Semantic series. Related: Famous Last Words: 640K, 65536, and the Ceiling You Are Drawing Right Now on the arrogance of fixed-width assumptions, Don’t Go Down With Your Iceberg on the cost of fusing to the substrate, You Actually Move Faster Without Breaking Things on the Schmiel move versus the 386 move, and The Watershed on how the kernel kept the track stable for thirty years.