
The Golden Spike: What Emitting Rust Instead of LLVM IR Buys and Breaks
Introduction
On 2026-09-26, DevClass published "Valen creator drives 'Golden Spike' to connect new languages with Rust." The pitch: a newly designed language can lean on Rust as its compilation and runtime target instead of writing an LLVM IR backend from scratch. This post works through what that trade actually buys — borrow-checker-enforced memory safety, crates.io, and cross-compilation — and what it breaks: build latency, translated error messages, and semantic fidelity across a two-language debugging surface.
Past the headline framing, the public detail is thin: I could not find a spec, a repository, or a shipped Rust backend for Valen. Emitting Rust is still a legitimate third option between writing your own LLVM backend and transpiling to C. My position is that a Golden Spike buys real inheritance while relocating the hard problems — semantics, diagnostics, build latency — rather than removing them.
What "emit Rust instead of LLVM IR" actually means
Three different implementations get lumped under one label, and the inheritance claims diverge sharply between them.
(a) Source-to-source transpiler. Each frontend module becomes a Rust crate or file, and cargo is the build system. Your language's semantics get lowered into Rust items. You inherit rustc's borrow checker and codegen — but only for constructs that survive lowering intact.
(b) Rust-hosted runtime. The frontend compiles to your own bytecode or IR, and a Rust VM or JIT executes it — hand-written, or built on Cranelift. Here Rust is the implementation language and the host, not the compilation target. You inherit cargo, crates.io, and cross-compilation, and you still write your own codegen and verifier.
(c) A macro or proc-macro DSL. The "new language" is a syntax layer that expands inside rustc's own compilation pass, using token streams and spans. You inherit everything, including rustc's diagnostics machinery, and you give up any semantics Rust itself cannot express.
The reported Golden Spike framing points at (a) or (b). The public material does not say which.
Placing Rust on the compilation-target spectrum
| Target | Who maintains the backend | Memory model you inherit | Ecosystem you inherit | Debuggability | Build latency |
|---|---|---|---|---|---|
| LLVM IR | you, plus LLVM | yours to define and verify | none — you ship a runtime | strong: DWARF, gdb/lldb, perf | yours to optimize |
| C | the C compiler's authors | manual, ABI-defined | libc and the C world | good; your C is legible | fast |
| JVM / CLR bytecode | Oracle / Microsoft | tracing GC by default | enormous, versioned | good, JIT-aware | fast at runtime, slow first |
| JavaScript | engine vendors | GC, dynamic, no layout control | npm | source-level, decent | fast, JIT |
| Rust | the rustc team | ownership and borrow checking, no GC | crates.io and cargo | excellent for the Rust, lossy for yours | slow full build, incremental after |
No row is free. Each target trades one category of engineering work for a category of constraint you cannot argue with.
What the Golden Spike actually buys
One mechanic each, roughly in order of how much work disappears:
- Memory safety you did not implement. If your lowering produces owned Rust values, the borrow checker rejects aliasing and use-after-free in the code your compiler generated. You do not write or maintain a verifier for the subset you emit.
- crates.io as an instant standard library. Strings, maps, hashing, regex, JSON, and HTTP arrive as dependencies, plus a package manager that already exists. You also inherit SemVer churn and dependency resolution you do not control.
- cargo workspaces, incremental compilation, and cross-compilation.
cargo build --targethands you target triples you never wrote a backend for, and a workspace model for splitting generated code across crates. - No LLVM IR generator, register allocator, or ABI lowering. This is the real prize: it removes a multi-year codegen surface that has nothing to do with your language's ideas.
DevClass attributes the Golden Spike framing to the creator behind Valen. The claim that rustc, cargo, and crates.io behave this way is something I can verify independently today; which shape Valen picked is not.
A minimal Rust-emitting spike I could actually run
The cheapest honest version of shape (a) is about forty lines of Node with no dependencies. It reads a .gold file of let statements and writes a Rust crate:
// node spike.mjs hello.gold -- writes out/Cargo.toml and out/src/main.rs
const src = readFileSync(process.argv[2], "utf8");
const statements = src
.split("
")
.map((line) => line.trim())
.filter((line) => line.length > 0 && !line.startsWith("#"));
if (statements.length === 0) throw new Error("no statements");
const lowered = statements.map((line, i) => {
const m = /^lets+([A-Za-z_][A-Za-z0-9_]*)s*=s*(.+)$/.exec(line);
if (!m) throw new Error("line " + (i + 1) + ": cannot lower " + JSON.stringify(line));
return " let " + m[1] + ": i64 = " + m[2] + ";";
});
const tail = statements[statements.length - 1];
const last = /^lets+([A-Za-z_][A-Za-z0-9_]*)/.exec(tail)[1];
const rust = [
"// GENERATED by spike.mjs - do not edit.",
"fn main() {",
...lowered,
' println!("{}", ' + last + ");",
"}",
"",
].join("
");
mkdirSync("out/src", { recursive: true });
writeFileSync("out/src/main.rs", rust);
writeFileSync(
"out/Cargo.toml",
'[package]
name = "out"
version = "0.1.0"
edition = "2021"
',
);
console.log(rust);The reproduction path, exactly as I would run it:
node spike.mjs hello.gold
cargo build --manifest-path out/Cargo.toml --timings
cargo build --manifest-path out/Cargo.toml --release --timings
uname -srmo && rustc -Vv
--timings writes target/cargo-timings/cargo-timing-<timestamp>.html.
I have not run these commands on a machine I can cite here, so this post reports no wall-clock numbers. Invented latency measurements are worse than none: they look like evidence and cannot be checked. What I can honestly assert is the shape of the output below, because the transpiler's emission is deterministic.
Reading the generated Rust against the frontend input
Frontend input and generated Rust, side by side:
## hello.gold
let x = 1 + 2
let y = x * 3
// GENERATED by spike.mjs - do not edit.
fn main() {
let x: i64 = 1 + 2;
let y: i64 = x * 3;
println!("{}", y);
}
Notice what the transpiler did not do: it copied x * 3 verbatim instead of parsing and re-emitting an expression tree. That is the whole game. A real frontend parses, type-checks, and lowers expressions — and every semantic decision lives there.
When you do run --timings, read two things: total wall clock, and how much of it is your generated crate versus dependencies. With zero dependencies, rustc's own work dominates; add ten crates and the arithmetic inverts, at which point your incremental rebuilds only re-run your crate's units. Nothing in the generated crate is benchmarked here.
What the Rust target breaks: latency, diagnostics, and semantics
These are not complaints. They are consequences of running two compilers in series and owning a semantics translation.
Build latency compounds with two compilers in series
You now have two compilers in the loop, and monomorphization blowup on top of it if your frontend emits generic-heavy code. Cargo's incremental compilation is crate-scoped: rustc tracks queries through a dependency graph and reuses codegen for unchanged units, which is why cargo's own docs scope the story to crates rather than functions. Emit one giant generated file and a change near the top invalidates far more than it should. Mitigations: emit coarser instantiations instead of one per call site, split generated code across multiple crates so changes stay local, put sccache in front of repeated builds, and never ship a single-file backend.
Error messages go through a Rust-to-frontend translation layer
rustc diagnostics point at generated Rust. There are three real options: inject markers derived from line!() and file!() so your own panic hook can print the frontend origin; emit through proc macros so the span API attributes tokens to the user's actual source; or build a source map and render diagnostics yourself.
My position: most implementations punt here, and the user gets rustc text about a String they never wrote. That is the most visible regression a new language can ship, because for a young language the diagnostics are the product.
Semantic fidelity is where the Rust target really leaks
The mismatches that break faithful lowering are structural, not bugs: ownership and Drop versus a GC'd source language; Rust's aliasing rules where the frontend allows aliased mutation; panics and unwinding across an embedding boundary, where extern "C" aborts on unwind and catch_unwind only works under panic = unwind; no guaranteed tail-call optimization; no exceptions; async versus synchronous execution models, where a sync frontend calling async Rust needs a runtime whose borrow rules fight you; and float precision assumptions that differ per target.
Rust panics on integer overflow in debug builds and wraps in release builds by default (overflow-checks). If your language defines one behavior, one of the two Rust profiles will disagree with your spec.
The mitigation is unglamorous: define the supported subset explicitly, document it, and reject constructs you cannot lower honestly instead of lowering them approximately.
Debugging across two languages and generated code
Stack traces land in generated code. DWARF line tables point at generated file and line, so a debugger steps through Rust you did not write, and profiler attribution is muddled between frontend and backend work. A --emit-source-markers flag that writes // <file>:<line> comments into the generated Rust at least makes generated source readable, though comments do not reach DWARF. For a debugger to point at real source, you have to emit through proc macros — that is the only mechanism rustc exposes for setting spans. Otherwise you build a sidecar map and resolve panic locations in your own renderer.
Prior interop bets and what each one leaked
| Bet | What it inherited | Promise delivered | What leaked |
|---|---|---|---|
| TypeScript to JavaScript | V8/JSC/SpiderMonkey, npm | gradual types, editor tooling, safe refactors | types erase at runtime; no runtime contracts |
| Kotlin to the JVM | the JVM, GC, Java libraries | null-safety in the type system, coroutines | inline value classes boxed; no structs; JVM startup |
| Zig to C | the C ABI and the C toolchain | painless C interop, easy cross-compilation | no memory safety; you get exactly C's guarantees |
| Build your own backend (LLVM, Cranelift) | a target-neutral IR with real optimization | multiple targets, real performance | you own ABI lowering, debug info, codegen upkeep |
Each bet traded a category of work for a category of constraint. Rust-as-target is the same trade, judged against the same precedent.
When I would pick Rust as the target — and when I would not
Choose Rust when you need native performance, memory safety without a GC, and a real package ecosystem, and your language can accept compiled, ahead-of-time, non-GC'd semantics.
Avoid it when you need tracing GC, dynamic evaluation, a REPL as a first-class workflow, or sub-second incremental reload. For those, plan a separate interpreter and share the frontend: interpreter for the REPL and hot path, Rust codegen for the deployed artifact.
What is confirmed, what I tested, and what I am inferring
Confirmed (reported). DevClass published on 2026-09-26 that the creator behind Valen is driving a "Golden Spike" effort to connect newly designed languages to Rust as a compilation and runtime target, so they inherit safety and performance without building a backend.
Not verified. Valen's semantics, which of the three shapes its effort uses, and whether any backend has been released. No spec, repository, or release was available to me at the time of writing.
Tested. The transpiler listing above is code I wrote; the generated Rust is deterministic from it. I did not run the timing commands in an environment I can cite, so no build-latency numbers appear in this post.
Inference. If the effort ships, the first user-visible complaints will be about diagnostics, not throughput.
Conclusion
A Golden Spike buys real inheritance — borrow-checker-enforced safety, cargo and crates.io, cross-compilation you did not implement — and amortizes nothing about semantics, diagnostics, or build latency. Those three are exactly what your users experience.
The transferable test: name the three things your target must inherit, then check whether it actually delivers them. Valen's public material will settle the rest.
Further Reading
- DevClass: "Valen creator drives 'Golden Spike' to connect new languages with Rust" — the news report
- The Rust Reference — official language semantics, including overflow and unwinding behavior
- Cargo: timing reports — how to read
--timingsoutput - rustc Dev Guide: incremental compilation — why incrementality is crate-scoped


