The signature stops being a suggestion.
In most languages, a function can quietly open a file or call out over the network, and nothing in its description warns you it does. BuildLang asks each function to declare it out loud. A function lists exactly what it’s allowed to touch; if it ends up reaching for something it never put on that list, the program is turned away, and the error points right at the line that broke the promise. Memory is treated the same gentle way: which piece of memory comes back to you, and how long it’s allowed to live, is checked rather than hoped for.
Two honest questions every function should answer (what am I allowed to touch, and whose memory am I handing you), answered by the compiler that runs the code, not by a comment that hopes you read it.Don’t take the type. Watch it refuse.
Each of these is a real file checked by the real binary: buildc, the release binary. The output is verbatim.
fn load_config() { read_file("ops.toml"); // reaches the filesystem } $ buildc check missing_effect.bld function `load_config` performs effect `FileSystem` but does not declare it help: add ~ FileSystem to the signature note: capability `FileSystem` was triggered by ambient call(s): read_file # declare the effect, and the same check passes, then writes a receipt fn load_config() ~ FileSystem { read_file("ops.toml"); } $ buildc check load_config.bld --receipt receipt.json Type checking... OK "declared_effects": { "load_config": ["FileSystem"] } "observed_capabilities": { "load_config": { "FileSystem": ["read_file"] } }
Touch your files without saying so, and it won’t build; the error points at the exact line that did it. Declare it instead, and it passes, and the compiler writes down what you promised, right next to what it actually saw you do.
Captured live from buildc check (Rust toolchain, release build) · the effect is built into the function and checked every time it’s called. That little receipt is the heart of it (what you promised, side by side with what really happened), and it’s honest that this is the foundation, not a finished, ship-it-tomorrow product.
fn pick<'a, 'b>(x: &'a i32, y: &'b i32) -> &'a i32 { y // returns 'b where the signature promised 'a } $ buildc check pick.bld Lexing... OK (28 tokens) Parsing... OK (1 items) Type errors found: function returns `&'b i32` but expected `&'a i32`
Here’s what’s happening: the function promised to hand back one specific piece of memory, then tried to hand back a different one that might disappear sooner. The compiler catches the swap and says no. How long memory is allowed to live is a promise too, and it’s kept.
Diagnostic from compiler/src/types/error.rs, surfaced through the lifetime matching in unify.rs. An honest limit, said plainly: this memory-lifetime checking is not yet complete. It already follows memory as it passes between functions (that’s the Phase 1 work), but I haven’t finished proving every case at the caller’s side, so I won’t claim it’s complete.
# an algebraic effect with a handler: parsed, effect-checked, # lowered to C, compiled, and run: $ buildc run examples/quickstart/effects_greeting.bld Hello, teammate!
That same promise-keeping follows the program the whole way down: read it, check what it’s allowed to touch, translate it into the C language, and run it. From there, one path turns it into a real program your machine can run on its own.
The C path is the one that works start to finish, and the tests prove it. The others (LLVM, WASM, SPIR-V, x86-64, ARM64, and a route that writes Rust) can produce code, but they’re still experiments. I’d rather just tell you that than let you assume otherwise.
# name a C library by its header and link flag, then call straight in: extern "C" link "sqlite3" header "<sqlite3.h>" { fn sqlite3_libversion() -> &str; // the header stays authoritative } fn main() ~ Foreign { let v = sqlite3_libversion(); } $ buildc build examples/ffi --emit c [4/4] Code generation (c)... OK (53517 bytes) # in the emitted C, verbatim: #include <sqlite3.h> // your header, included as-is // buildc-link: sqlite3 // recorded, so `buildc build` links it _1 = sqlite3_libversion(); // the real call into the C library # the reverse is `extern "C" fn` (C calls BuildLang). and a live run # straight through a C variadic: $ buildc run examples/variadic/main.bld the answer is 1 and 2
Name a C library by its header and link flag, and BuildLang includes the header, records the link, and calls straight in. extern "C" fn goes the other way, so C can call back. A foreign call is still an effect: main declares ~ Foreign, so reaching into C sits behind the same check as touching a file.
Captured live from buildc build --emit c and buildc run (release build). buildc emits the include, the link directive, and the call, checked end to end through the build; the final native link against sqlite3 is your own C toolchain’s job, not something the test suite claims for you.
The receipt grew a second kind: accountable scientific compute.
The little check receipt from Exhibit I says what a program promised, next to what it did. There’s now a second receipt for a different question: when a program crunches numbers, did the numbers actually behave the way the math says they should? buildc run --emit-receipt runs a numeric .bld kernel, captures the whole output series, checks a stated invariant (a rule the numbers must obey), and seals a re-checkable JSON receipt. buildc receipt verify runs the program again and re-derives the verdict from scratch, so drift, tampering, or a changed source each fail with their own named class. buildc receipt export re-verifies and hands witnessed measurements over to Crucible, the bridge into the wider Telos work.
Here is the honest edge of it, said before you ask. The receipt witnesses that this compiled program’s observed output series satisfied the invariant (or violated it exactly where a negative kernel was meant to). It does not prove the underlying equations are right, and it does not announce a physical law. Every receipt carries a NOT_A_NEW_PHYSICAL_LAW label so no one can quote it as more than it is.
# a heat-diffusion kernel: its energy should never climb. run it, # check the invariant, seal a receipt: $ buildc run heat.bld --emit-receipt heat.json --invariant energy-monotone Running... OK (240 steps captured) Invariant energy-monotone... HELD "label": "NOT_A_NEW_PHYSICAL_LAW" "verdict": "HELD" // witnesses the OUTPUT SERIES, not the PDE # re-run from scratch and re-derive the verdict. change a number and it fails: $ buildc receipt verify heat.json Re-running program... OK Re-deriving verdict... MATCH # drift / tamper / source-change each fail typed
The receipt doesn’t ask you to trust a stored answer. Verify re-runs the program and works the verdict out again, so a nudged number, an edited receipt, or a changed source can’t slip through, each trips its own named failure.
Captured live from buildc run --emit-receipt and buildc receipt verify (release build) · the JSON is re-checkable and every receipt is stamped NOT_A_NEW_PHYSICAL_LAW. The plain limit: this witnesses the compiled program’s observed output series against a stated rule; it does not prove the equations, and buildc is not a finished replacement for Julia.
# every invariant ships paired: a kernel that should HOLD and one that # should VIOLATE, so a verdict of HELD actually means something. energy-monotone # the series never climbs past tolerance conservation # stays within roundoff of its starting value bounded # never exceeds the initial value (a discrete maximum principle) energy-identity # per-step energy balance holds to roundoff // the FTCS analogue of d/dt ∫u² = -2a ∫u_x² relation --columns N # N reported columns must agree, so a run can't hide a divergence conserved-band # approximate conservation inside a fixed error budget # conserved-band grounds symplectic integration, and it shows the point: $ buildc run leapfrog.bld --emit-receipt lf.json --invariant conserved-band Invariant conserved-band... HELD # energy stays in an O(dt²) band $ buildc run euler.bld --emit-receipt eu.json --invariant conserved-band Invariant conserved-band... VIOLATED # explicit Euler drifts out and fails, as it should
A verdict is only worth trusting if it can say no. Each invariant carries a matched pair: a kernel meant to hold and one meant to break, at a fixed tolerance, so a green result is a real result and not a rubber stamp.
Six invariants · energy-monotone, conservation, bounded, energy-identity, relation --columns, and conserved-band, each with paired positive and negative kernels · the leapfrog-versus-Euler contrast is the honest demonstration that conserved-band actually distinguishes a good integrator from a drifting one. Still true here: a receipt witnesses observed output, not a proof of the physics.
# re-verify, then hand witnessed measurements to Crucible (the Telos bridge): $ buildc receipt export heat.json --to crucible Re-verifying... OK Emitting witnessed measurements... OK "witnessed": true // re-derived here, not trusted from the file "label": "NOT_A_NEW_PHYSICAL_LAW"
Export doesn’t forward a stored claim. It re-verifies first, then emits measurements Crucible can stand behind, carrying the same honest label the whole way across.
The backends, said plainly: C is the only production-verified path; HLSL and GLSL cover shaders; SPIR-V, LLVM, WASM, Rust, x86-64, and ARM64 stay experimental. cargo install buildlang is accurate (buildlang 1.1.0 is on crates.io), but the published crate predates this receipt work, so don’t read the newest invariants into the version you install today.
1,521 passing tests, and an honest list of what doesn’t ship yet.
Built in Rust. The Cargo suites pass green at the current baseline: 940 library, 307 CLI, 134 binary, 88 parser, and 52 lexer tests, 1,521 in all, 0 failed. The runnable scientific corpus passes 8 of 8. The C path carries a program all the way to something your machine can run on its own, and it’s the route the tests check from one end to the other. That’s the whole of what I’ll claim, and nothing the tests won’t back up.
It isn’t winding down, and it isn’t standing still. The core holds steady while the ground underneath it keeps deepening.
The core is shipped and steady: say what you’ll touch, be clear about your memory, checked the whole way down to native code. That’s solid ground, and lately the ground itself has been growing. BuildLang now speaks C both ways: declare a C library with its header and link flag and call straight into it, or expose a function as extern "C" so C can call back, all emitted and checked through buildc build. Linear types landed too, an opt-in no-cloning rule that’s the shared keystone for quantum, financial, and on-chain work (experimental, not yet fully sound, and I say so plainly). The first real step toward automatic memory cleanup is in as well, opt-in and deliberately narrow for now. I’d still rather you hear the rough edges from me than stumble on them: doing many things at once is parsed and type-checked, but its runtime isn’t wired into compiled programs yet; the compiler meant to one day build itself is drawn up but doesn’t run; and the experimental backends can make code but aren’t ready for real use. I built this on my own. These are the named limits of a foundation that’s still deepening, not a project closing its doors.
Build it. Then make it reject something.
The quickest way is cargo install buildlang from crates.io; you’ll also want a C compiler installed where the system can find it. Or build from source. Either way, buildc doctor tells you the C path is good to go. Point it at the examples, or, better, write a function that fibs about what it touches, and watch it gently refuse.
$ cargo install buildlang # from crates.io; installs the buildc binary # or from source: $ git clone https://github.com/HarperZ9/buildlang && cd buildlang/compiler $ cargo build --release $ buildc doctor # toolchain + C-backend readiness C backend: ready $ buildc run examples/quickstart/effects_greeting.bld Hello, teammate!