Programming-Language Strategy
Four languages across 34 months, each assigned to projects by which mechanism it makes visible — not by preference, and not revisited on a whim.
Table of Contents
- The Principle
- The Assignment
- Why Each Language, Specifically
- The Rust Learning Cost, Budgeted
- Rewrites: When They Are Allowed
- Cross-Language Boundaries
- What Is Not On This List, And Why
- References
The Principle
Choose the language that makes the mechanism under study impossible to ignore.
A language is a lens. Python hides memory, so it is the wrong choice for a garbage collector and the right choice for an algorithm you want to see clearly. Rust makes lifetimes explicit, which is unbearable overhead for a numerical experiment and exactly right for a storage engine. Go makes concurrency cheap, so you build the real topology instead of a simplified one.
The corollary is a rule the plan enforces: no rewrites for aesthetic reasons. Every hour spent reimplementing a working component in a nicer language is an hour not spent on a mechanism, and "constantly changing programming languages" is one of the fourteen named failure modes.
The Assignment
| Language | Projects | Hours | Share |
|---|---|---|---|
| Python | P01, P13, P08, P09, P10, parts of P02/P03 | ~470 | 33% |
| Rust | P04, P11, P12, P02's hot loop, P03's storage layer | ~430 | 30% |
| Go | P05, P06, P07 | ~319 | 22% |
| C / CUDA / Metal | P14, optionally P12 | ~77 | 5% |
| Mixed | P15 | ~143 | 10% |
You already know Python and Go professionally. Rust is the single real learning cost in the plan, and it is paid deliberately in the easiest project.
Why Each Language, Specifically
Python — P01, P13, P08, P09, P10
What it exposes: the algorithm, by making everything else uniform.
In P01 you want to see attention, not memory management. In P08 and P09 you want to change a ranking policy in ten minutes and rerun. Python's ecosystem — numpy, matplotlib, scipy — means the experiment harness is not itself a project.
And its slowness is a feature here. Python makes constant factors impossible to ignore, which produces two of this journey's best results:
- P02's two-factor decomposition: the graph index does 6.9× fewer distance computations and is 10× slower, because each one costs 899 ns instead of 13.3 ns. In C++ that gap would have been invisible and the lesson unlearned.
- P11's dispatch measurement: a bytecode VM written in Python is 1.5× slower than a tree-walk interpreter written in Python, which is the clearest possible demonstration that bytecode's advantage is cheap operations rather than fewer operations.
Neither of those results is available in a fast language. Python's overhead is a magnifying glass.
Where it is the wrong choice: anything where GC pauses, memory layout, or true parallelism are the subject. That is the rest of the list.
Rust — P04, P11, P12
What it exposes: lifetime, ownership, and aliasing — made explicit by the compiler.
Three reasons, in order of importance:
- The borrow checker forces the questions the projects are about. A storage engine is a set of decisions about who owns a buffer and how long it lives. Rust will not let you defer those decisions. In C you can defer them until a Tuesday-night segfault.
- No GC, so your latency measurements are yours. In P11 you are building a garbage collector and measuring its pauses. A runtime with its own GC would confound every number. In P12, an allocator in the runtime is simply not available.
- It is the language of the domain now. Storage engines, runtimes, and kernels are being written in Rust, and the transferability is real.
The honest cost: Rust is slower to write than C for the first three months. The plan absorbs that by scheduling P11 Phase I — the conceptually easiest project — as the on-ramp.
Why not C? C would work for all three. Rust is chosen because the compiler teaches — every borrow-check error is a question about lifetime that you would otherwise have answered wrong and found out later. That feedback loop is worth the friction in a learning program, and it is exactly the wrong trade in a deadline-driven one.
Go — P05, P06, P07
What it exposes: concurrency, cheaply enough that you build the real topology.
- Goroutines make the real design affordable. A 5-node Raft cluster with per-peer RPC loops, heartbeat timers, and an apply loop is natural in Go and painful in Rust's async ecosystem. You end up building the system you designed rather than the one your concurrency model permitted.
- The tooling is genuinely good for this.
go test -racefinds real data races.pprofis excellent.contextgives you cancellation and deadlines without ceremony — and deadline propagation is a distributed-systems concept, not a language feature. - Deterministic simulation is straightforward. P05's in-process simulated network with a seeded scheduler — the thing that makes distributed bugs replayable — is a comfortable Go program.
- You already know it, so the language costs nothing while distributed systems cost everything. That is the right allocation of difficulty for the hardest stage.
Where Go's GC could confound you: P05's tail-latency measurements will include GC
pauses. That is realistic — every production distributed system in a managed language has
this — but you must measure and report the GC contribution rather than let it hide in
your p99. GODEBUG=gctrace=1 and a note in the report.
C, CUDA, Metal — P14
What it exposes: nothing. And that is the point.
For hardware-aware work you need the memory hierarchy, vectorisation, and kernel launch
to be explicit and unmediated. No borrow checker, no bounds checks, no runtime. The
compiler flag table on
P14's page
— where blocking is worse than loop reordering at -O2 and 2.1× better at -O3 -mcpu=native — is only legible when you can read the generated assembly and change one
flag at a time.
CUDA if you have an NVIDIA GPU; Metal on Apple Silicon; otherwise the simulator carries the learning objective.
The Rust Learning Cost, Budgeted
Rust is the only genuinely new language, and pretending it is free is how plans slip.
| When | Project | What Rust costs you | Why it is affordable there |
|---|---|---|---|
| W16–20 | P11-I (Small, 55 h) | ~15 h of pure language friction | The project is conceptually easy: a lexer, a parser, a tree walk. The language is the only hard thing, so all the difficulty is in one place |
| W35–43 | P04 (Medium, 99 h) | ~8 h | You now have Rust basics; the difficulty moves to storage |
| W44–50 | P11-II | ~4 h | Fluent enough that lifetimes are a tool, not an obstacle |
| W100–110 | P12 (Large) | ~0 h | Two years in. no_std is new; the language is not |
Total budgeted language-learning overhead: ~27 hours, front-loaded into the smallest project. Already included in the effort estimates.
The specific thing to expect in P11-I: an environment chain with parent pointers,
captured by closures, requires Rc<RefCell<Environment>>. You will fight this for a day
and then understand why — shared mutable ownership with runtime-checked borrowing is
exactly what a mutable scope chain is. Do not cargo-cult it from a tutorial; derive it
from the error messages. That day is the highest-value day of Rust learning in the plan.
Rewrites: When They Are Allowed
Default: never. A working component stays in the language it was written in.
Three exceptions, each requiring a written justification in the project's report:
| Exception | Example | Justification required |
|---|---|---|
| The measurement demands it | P02's inner loop in Rust via PyO3, after Python established the two-factor model | The rewrite is the experiment (E11: does the constant factor collapse as predicted?) |
| A later project needs a different boundary | P03's storage layer in Rust while the index stays Python | Named which later project, and why the boundary must move |
| The original language made a mechanism invisible | Discovering mid-project that GC pauses confound your measurements | State what became invisible and why you did not foresee it |
Note that the first exception is a rewrite of a hot loop, not of a project. Rewriting 1,000 lines to prove a point is not the same as rewriting a 5,000-line index because Rust is nicer.
What is never a justification: "it would be cleaner", "I want more Rust practice", "the types would be better". Those are real preferences and they are not worth a week that belongs to P12.
Cross-Language Boundaries
P15 integrates four languages. Two ways across, and the choice matters more than it looks.
| Approach | Use when | Cost |
|---|---|---|
| Process boundary — separate processes, a simple protocol (JSON or length-prefixed binary over a Unix socket or stdin/stdout) | The default. Almost always right | One IPC round trip per call. Measure it and put it in the latency budget as a line item |
| FFI — PyO3 (Rust↔Python), cgo (Go↔C) | Only when the call is in an inner loop and IPC would dominate | Build complexity, memory-safety issues at the boundary, and debugging that spans two runtimes |
Prefer process boundaries in P15. A subprocess call is a known, measurable cost you can budget for; a broken FFI binding is an unbounded cost you cannot. The only place FFI is clearly correct in this plan is P02's distance kernel, where the whole point is to remove a per-distance overhead of 899 ns — an IPC boundary there would be absurd.
One rule for either approach: the boundary is a place where errors must be translated,
not propagated. A Rust Result and a Python exception are not the same object, and
deciding what a failure means at the seam is a design decision to make explicitly.
What Is Not On This List, And Why
| Language | Why not |
|---|---|
| C++ | Overlaps Rust's role without teaching anything additional here, and its complexity budget is better spent on domains. Read C++ (LevelDB, hnswlib, FAISS); do not write it |
| Zig | Genuinely appealing for P12, and the ecosystem and stability are not worth the risk in a 34-month plan. Revisit after |
| Java / Kotlin / Scala | You know the JVM professionally. It hides exactly the things P04, P11 and P12 study |
| JavaScript / TypeScript | No mechanism in this journey is best exposed by it |
| Haskell / OCaml | OCaml is an excellent language for P11 specifically, and adding a fifth paradigm costs more than P11 gains |
| Assembly | Read it in P14 (-S, and the vectoriser reports). Do not write it |
| Verilog / VHDL | Only if you take P14's FPGA extension. Not required |
The list is short on purpose. Four languages over 34 months is already a lot of context switching; six would mean never becoming fluent in any of the new ones.
References
- Klabnik, S., Nichols, C. The Rust Programming Language, 2nd ed. No Starch Press, 2023. Chapters 4, 10, 15 (ownership, lifetimes, smart pointers) before P11-I.
- Blandy, J., Orendorff, J., Tindall, L. Programming Rust, 2nd ed. O'Reilly, 2021. Better than the book above for a systems programmer who already knows C-family languages.
- Donovan, A. A. A., Kernighan, B. W. The Go Programming Language. Addison-Wesley, 2015. Chapters 8–9 on concurrency.
- Cox, R. Go Data Structures: Interfaces and the Go blog's memory-model posts. Relevant to P05's correctness reasoning.
- Kernighan, B. W., Ritchie, D. M. The C Programming Language, 2nd ed. Prentice-Hall, 1988.
- NVIDIA. CUDA C++ Programming Guide. For P14 if you have the hardware.
- Sapin, S. et al. The Rustonomicon. For P12's
no_stdand unsafe work. - Van Roy, P., Haridi, S. Concepts, Techniques, and Models of Computer Programming. MIT Press, 2004. The argument that languages are lenses rather than tools — the intellectual basis for this page's principle.