Safer C.
No rewrite required.

Drop into an existing C codebase. Existing C compiles unchanged.
Prism adds defer, orelse, and zero-initialization on top.

prism run main.c
version
v1.1.5
tests
13,685
commits
681
deps
0
license
Apache 2.0
defer

Cleanup that can't be forgotten.

Bound to scope, not to every exit path. Runs in reverse order at scope end, whether you return early, fall through, or hit an error branch.

−18lines avg
3bug classes closed
Before
FILE *f = fopen(path, "r");
if (!f) return -1;

int r = parse(f);
if (r < 0) {
    fclose(f); // easy to forget
    return -1;
}

fclose(f);
return r;
After
FILE *f = fopen(path, "r")
    orelse return -1;
defer fclose(f);

return parse(f);
Before
char *buf = malloc(n);
if (buf == NULL) {
    log_error("OOM");
    return -1;
}
// ... 20 more lines
After
char *buf = malloc(n)
    orelse return -1;
orelse

Inline error handling without the noise.

Checks any falsy scalar (null pointer, zero integer, zero return code) inline. No nested if-blocks, no repeated cleanup calls, no diverging error paths. Works with return, break, continue, goto, or a fallback value.

−12lines avg
2bug classes closed
zero-init

No garbage values. Ever.

Every local variable is zeroed at declaration: scalars, pointers, arrays, structs. The entire class of bugs from uninitialized reads is simply closed.

less undefined behavior
1bug class closed
Before
typedef struct {
    int width;
    int height;
    int flags; // garbage
} Config;

Config cfg; // uninitialized
if (cfg.flags & VERBOSE) {
    // undefined behavior
}
After
typedef struct {
    int width;
    int height;
    int flags;
} Config;

Config cfg; // zeroed automatically
if (cfg.flags & VERBOSE) {
    // always safe: flags == 0
}
C (compiles, silent leak)
int process(const char *path) {
    FILE *f = fopen(path, "r");
    if (!f) goto done;
    defer fclose(f); // skipped!
    do_work(f);
done:
    return 0; // f never closed
}
Prism (compile error)
int process(const char *path) {
    FILE *f = fopen(path, "r");
    if (!f) goto done;
    defer fclose(f);
    do_work(f);
done:
    // error: goto 'done' skips over
    // defer fclose(f)
    return 0;
}
safety

Control flow Prism catches.
C doesn't.

CFG analysis runs before any output is emitted. goto jumping over declarations, switch fallthrough skipping active defers, defer in setjmp/vfork/asm functions: all compile errors, not silent bugs.

0runtime overhead
all errors before emission
raw

Opt out. Precisely.

Zero-init has a cost on large buffers. raw skips initialization for a single variable without disabling anything globally.

per-variablegranularity
0global flags needed
// zeroed by default: 64k wasted memset
char buf[65536];
ssize_t n = read(fd, buf, sizeof(buf));

// raw: skip it: read fills it anyway
raw char buf[65536];
ssize_t n = read(fd, buf, sizeof(buf));
check

Static analysis that
sees your cleanup.

Prepend prism check to the analyzer you already run. Source arguments are transpiled to standard C behind the scenes, every other flag passes through untouched, the tool's exit code is preserved, and #line directives map each finding back to your original line numbers.

Analyzing the expansion beats analyzing the source: the analyzer sees the real control flow, cleanup included. A manual fclose(f) before an early return alongside defer fclose(f); comes back as doubleFree. No source-level C analyzer catches that, because none of them parse defer.

1 wordto prepend
0build changes
prism check cppcheck --enable=all src.c

// src.c:12:4: error: Array 'arr[8]' accessed
//            at index 8, out of bounds

// The finding cites src.c line 12, not a line
// in the generated artifact.

13,685 tests. Adversarially.

Not happy-path coverage. Edge cases, degenerate control flow, and patterns specifically designed to break a transpiler.

Prism is open source under Apache 2.0. Read every line, fork it, ship it. No strings.

Real-world codebases

OpenSSL, SQLite, Bash, Curl, GNU Coreutils, and Make, compiled unmodified through Prism.

Control flow nightmares

Nested loops with break and continue, switch fallthrough into deferred scopes, goto jumping over declarations, statement expressions inside defer bodies, computed goto with active defers. Every combination that could silently misfire.

Typedef disambiguation

Every typedef, enum constant, VLA tag, and parameter shadow: registered at all depths before any output is emitted. size_t x; and size_t * x; are not the same thing. Prism knows that.

Self-hosting

Prism compiles itself, four stages deep. stage0 through stage3 produce byte-identical transpiled output: 1,058,598 bytes, the same on Linux x86_64, macOS x86_64/arm64, Linux arm64, and Linux riscv64. If it couldn't compile itself correctly, it couldn't ship.

GitHub

Built to spec.

Prism has a formal specification: every feature, edge case, and invariant documented and cross-referenced against the test suite.

v1.1.5 · 13,685 tests · self-hosting · 8 invariants

Read the spec

Tokenizer, Pass 1 analysis, CFG verification, Pass 2 codegen, all language semantics.

Documentation

Getting started, CLI reference, feature guides, and architecture overview.

Blog · Apr 2026

A pragmatic C

On C's defaults, marginal improvements, and why Prism exists.

Continuous integration across Linux, macOS, and Windows on x86_64, arm64, and riscv64.

passing
b2a657b - fix test suit issue updated 27 Jul 20:39
10m 12savg run time
6platforms
Linuxx86_64success2m 0s
Linuxarm64success1m 33s
Linuxriscv64success31m 41s
macOSx86_64success3m 8s
macOSarm64success2m 36s
Windowsx86_64success2m 21s

Prism is built and maintained by one person, in the open, for free.

If it saves you time or makes your C safer, sponsoring is how you keep that going. Every sponsor directly funds time on new features, bug fixes, and keeping the test suite green.

Available for consulting work across design, branding, and engineering.

Compiler work, systems programming, C codebase hardening, dev tooling, or a visual identity for your project. Send an email.

dawn@dawn.day

Serious inquiries only.