The geotechnical engine behind Terraforge is one Rust workspace that compiles to a native binary for the server and a WebAssembly module for the browser. Same code, same types, two runtimes. And it's the language I'd most want an LLM writing — because the compiler won't let it bluff.
// the duplication problem
Any product with real domain logic eventually wants to run that logic in two places. On the frontend, so the user gets an instant answer as they drag a slider — no round-trip, works offline, no server cost per keystroke. On the backend, so you can validate, batch, generate reports, and not trust a number a browser computed.
The lazy answer is to write it twice — once in TypeScript, once in whatever the server speaks. Now you have two implementations of a bearing-capacity formula that must agree to the decimal, and they will quietly drift apart the first time someone fixes a bug in one and forgets the other. For a geotechnical engine, "quietly disagree about a foundation's safety factor" is not an acceptable failure mode.
// the insight
Rust compiles to native code and to wasm32-unknown-unknown — WebAssembly that runs in every browser, in Node, in Workers, in Wasmtime on a server. So you write the algorithm once, in a strongly-typed language, and ship the exact same compiled logic to both sides. There is no second implementation to drift.
The Terraforge compute core is ten pure-Rust crates — bearing capacity, CPT correlations, settlement, slope stability, pile capacity, a 2D FEM solver, and so on. None of them know what WebAssembly is. They're just functions over typed structs with serde on the boundary. A single thin facade crate wraps them for the browser:
crates/ ├── core/ shared types — soil, CPT, units, geometry ├── bearing/ Terzaghi / Meyerhof / Hansen / Vesic ├── cpt/ Robertson soil-behaviour-type + correlations ├── settlement/ elastic + consolidation ├── slope/ Bishop / Fellenius ├── fem/ 2D plane-strain solver │ … six more pure-compute crates … └── wasm-bridge/ the ONLY crate that knows about the browser
// the boundary
The facade does one boring thing per function: take JSON in, call the compute crate, hand JSON back. Strings at the edge, typed Rust in the middle.
#[wasm_bindgen]
pub fn bearing_capacity(input_json: &str) -> Result<String, JsValue> {
let input: BearingInput = serde_json::from_str(input_json)?; // parse
let result = terraforge_bearing::solve(&input); // pure Rust
Ok(serde_json::to_string(&result)?) // serialize
}On the frontend that's two lines of TypeScript:
import init, { bearing_capacity } from "./pkg/terraforge_wasm.js"
await init()
const out = JSON.parse(bearing_capacity(JSON.stringify(input)))On the backend you skip the WASM entirely and call terraforge_bearing::solve directly — it's the same crate, now linked into a native binary instead of a .wasm. The browser and the server are running byte-identical logic, because it's the same source compiled twice. JSON strings at the boundary are a deliberate choice: language-neutral, so a Python or Go backend can speak to the same module without caring it's Rust underneath.
// why rust is good with LLMs
People assume Rust is a worse fit for AI codegen because the borrow checker is famously strict. It's the opposite. An LLM writes plausible code — that's the whole risk. Plausible Python runs, looks right, and is wrong in a way nobody notices until production. Rust's type system and borrow checker reject a huge class of "looks right, is wrong" before it ever runs.
That makes the loop tight and self-correcting. The model proposes, the compiler objects with a specific, located error, the model fixes it. The pain that Rust is famous for inflicting on humans is exactly the signal an agent needs: a fast, precise, unforgeable "no." No null slipping through. No silent type coercion. Exhaustive match arms the model can't forget. The compiler does the review a human reviewer is too tired to do at 2am.
// why not just ship python
"But the algorithms already exist in Python." Sure — and you can run Python in the browser now, via Pyodide (CPython compiled to WASM). I tried it. It works, and it brings every disadvantage of Python with it.
.wasm — kilobytes of logic, not a whole interpreter.Pyodide is the right answer when you must run a specific Python library client-side. For logic you own and want fast, small, and type-checked on both ends, Rust is the better trade.
// what you get
// what you give up
wasm-pack sits between your Rust and your bundle. It's one command, but it's one more thing than "just import a .ts file."// when this pattern fits
Anywhere you have non-trivial logic that must run in the browser and on the server and must agree: engineering calculations, pricing and tax, validation rules, simulations, parsers, geometry, anything where "the two copies disagree" is a real bug. Write it once in Rust, keep it pure, wrap a thin JSON facade, compile twice. The hard part — the algorithm — exists in exactly one place, and a machine that can't be talked out of its objections checks it every time you build.
This is the engine behind Terraforge →. For the map side of the same project, see map tiles without a tile server →
Note: this article was written by an AI. The architecture is real and shipping in production at Terraforge — the prose isn't hand-written.