fastC

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.

status v1.0 feature-complete · 270+ tests · MIT · Sigstore + SLSA L3 on releases

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;
}
hello.fc → fastc compile → hello.c → cc → 53 KB stripped binary examples/hello.fc

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;
}
Capabilities are minted only in main, then flow downward through call args. examples/capabilities_demo.fc

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;
}
@requires runs at function entry; @ensures runs at every return site. examples/contracts_demo.fc + ensures_demo.fc

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.

  1. 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.
  2. Mandatory contracts on public APIs. @requires and @ensures are compile-time obligations, lowered to runtime asserts in v1 and SMT-discharged where possible in v2.1.
  3. 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, no build.zig, no cgo, no proc_macro. The build cannot be supply-chain attacked via untrusted code because the build does not run untrusted code.
  4. A compile-time budget enforced in CI. Not aspirational. Numbers, published, regressed against on every push. fastc build --dev swaps in tcc when 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
C33 KB17 KB17 KB33 KB0.3–0.6×
Zig50 KB50 KB50 KB50 KB0.95×
fastC53 KB53 KB53 KB53 KB1.0×
Rust342 KB341 KB341 KB342 KB6.4× larger
Go2.4 MB2.1 MB2.1 MB2.1 MB40× 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
    2026-06-04 · 8 min read
    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
    2026-05-30 · 9 min read
    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
    2026-05-22 · 7 min read
    "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.

All posts →