Skelf-Research · language project · v1.0 feature-complete
C, but safe and
agent-friendly.
fastC is a small systems language with capability-typed I/O, mandatory contracts, and zero executable build scripts. It compiles to portable C11. It refuses to run anything at build time. A function with no capability arguments structurally cannot do I/O — not because a sandbox blocked it, because the compiler rejected the call.
The shape of a fastC program
Looks like C. Behaves like a proof obligation.
A fastC source file uses C-shaped syntax — fn, let,
i32, braces, semicolons. It compiles to readable C11
you can audit. What changes is everything around the body:
the signature now carries capabilities, contracts, and a fixed set of
annotations that the compiler treats as obligations.
A hello-world, sized in bytes
// hello.fc — a fastC program is plain C-shaped, but with
// explicit types everywhere and no implicit conversions.
fn add(a: i32, b: i32) -> i32 {
return (a + b);
}
fn main() -> i32 {
let x: i32 = 10;
let y: i32 = 20;
let result: i32 = add(x, y);
return result;
} 53 KB stripped on M3. Same class as plain C (33 KB) and Zig (50 KB). Rust ships 342 KB for the same program; Go ships 2.4 MB.
I/O as a typed argument, not ambient authority
// I/O capabilities are typed function arguments. A function
// without a CapFsRead in its signature structurally cannot
// read a file. The compiler refuses the call.
use caps::init;
fn count_files_read(c: ref(CapFsRead)) -> i32 {
discard(c);
return 42;
}
fn main() -> i32 {
let caps: Caps = init(); // root caps minted in main
let n: i32 = count_files_read(addr(caps.fs_read));
return n;
} CapFsRead, CapNetConnect, CapProcSpawn —
the capability set is finite, named, and only mintable in main
via caps::init().
Contracts the compiler holds you to
// Pre- and postconditions are compile-time obligations.
// v1 lowers to runtime asserts; v2.1 discharges via Z3 where
// it can — proven obligations cost zero at runtime.
@requires(divisor != 0)
@requires(divisor > 0)
fn safe_div(value: i32, divisor: i32) -> i32 {
return (value / divisor);
}
@ensures(result >= 0)
fn abs(x: i32) -> i32 {
if (x < 0) {
return (0 - x);
}
return x;
}
Three-tier discharge: a syntactic pass catches the free wins (constant fold,
tautological comparison), Z3 handles linear-integer tautologies under a
500 ms budget, and anything unproven falls back to a runtime
fc_trap. The build emits a discharge.json per-obligation
report so you can see which clauses cost nothing at runtime and which still do.
The wedge
Four things Rust cannot retrofit.
fastC is not "Rust minus features." It is a smaller language designed from the start around four properties that a 150K-crate ecosystem cannot adopt without breaking itself.
- Capabilities as typed function arguments. No ambient
fs::read. A function without a capability argument is structurally inert — the type system records what it cannot do. - Mandatory contracts on public APIs.
@requiresand@ensuresare compile-time obligations, lowered to runtime asserts in v1 and SMT-discharged where possible in v2.1. - No executable build scripts. No central registry. Dependencies are
git URLs with commit + sha256 + cosign keyless signing, vendored into the
project tree. There is no
build.rs, nobuild.zig, nocgo, noproc_macro. The build cannot be supply-chain attacked via untrusted code because the build does not run untrusted code. - A compile-time budget enforced in CI. Not aspirational. Numbers,
published, regressed against on every push.
fastc build --devswaps intccwhen available for sub-10ms C steps.
Measured
Binary size, where the wedge is sharpest.
Snapshot from the cross-language benchmark suite on M3 (2026-05-22). Stripped binary in KB. fastC sits in the C / Zig class, not the Rust / Go class.
| Language | hello | sum | fib(40) | mandelbrot | vs fastC |
|---|---|---|---|---|---|
| C | 33 KB | 17 KB | 17 KB | 33 KB | 0.3–0.6× |
| Zig | 50 KB | 50 KB | 50 KB | 50 KB | 0.95× |
| fastC | 53 KB | 53 KB | 53 KB | 53 KB | 1.0× |
| Rust | 342 KB | 341 KB | 341 KB | 342 KB | 6.4× larger |
| Go | 2.4 MB | 2.1 MB | 2.1 MB | 2.1 MB | 40× larger |
Compile time runs ~30–40 % faster than Rust to a release binary. Runtime matches C on floating-point work; ~26 % slower on recursive integer (overflow-check cost). Full methodology and re-run scripts live at benchmarks/cross-lang/.
Why now
The producer is stochastic. The reviewer is stochastic.
The compiler is the only deterministic step in the loop.
01 Signature = operating manual
A fastC signature tells an agent which memory it touches, which I/O it can do, what must hold on entry, what is guaranteed on exit, whether it can panic, and its complexity bound. The body is implementation detail.
02 One canonical idiom
The fastc-core launch set ships one curated answer per
domain: cli, log, json, toml, http. No
ecosystem research, no choosing between 12 logging crates.
03 Typed protocol for agents
fastc-mcp exposes the build artifacts —
caps.json, discharge.json, structured diagnostics — to
Claude Code, Cursor, and Codex over Model Context Protocol. No
text-parsing of cargo check.
Compare
Pick your fight, fairly.
fastC is not "better than X on every axis." Each of these comparisons shows the trade-offs honestly — where fastC wins, where the other language wins, and which threat model each was built for.
vs Rust
The obvious comparison. Rust has more safety machinery and a vastly larger
ecosystem; fastC has structural answers to build.rs, capabilities,
and compile-time budgets that Rust cannot retrofit.
vs Zig
Zig is the closest in spirit on binary size, cross-compilation, and "no
hidden control flow." fastC chooses capability typing and mandatory contracts
over comptime as the wedge for agent-generated code.
Recent writing
Notes on the language design and how it interacts with code-writing agents.
- What goes wrong when LLMs write C: the recurring bugs The bug shapes we see over and over when an agent generates C — and which of them the fastC type system structurally prevents, which it converts into a build-time trap, and which it just makes louder.
- Reading fastC: side-by-side syntax with plain C A column-by-column tour. If you can read C, you can read fastC in twenty minutes — but the things that look the same do not always mean the same thing.
- Why agent-friendly is a real language design goal "Designed for LLMs" sounds like marketing. It is not. Here is the concrete list of language-design choices that change when the modal author of code is a stochastic process.