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.
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;
FILE *f = fopen(path, "r")
orelse return -1;
defer fclose(f);
return parse(f);
FILE *a = fopen(pa, "r");
if (!a) return -1;
FILE *b = fopen(pb, "r");
if (!b) {
fclose(a); // must remember order
return -1;
}
process(a, b);
fclose(b);
fclose(a);
FILE *a = fopen(pa, "r")
orelse return -1;
defer fclose(a);
FILE *b = fopen(pb, "r")
orelse return -1;
defer fclose(b);
process(a, b);
pthread_mutex_lock(&m);
if (x < 0) {
pthread_mutex_unlock(&m);
return -1; // deadlock if missed
}
do_work();
pthread_mutex_unlock(&m);
pthread_mutex_lock(&m);
defer pthread_mutex_unlock(&m);
if (x < 0) return -1;
do_work();
char *buf = malloc(n);
if (buf == NULL) {
log_error("OOM");
return -1;
}
// ... 20 more lines
char *buf = malloc(n)
orelse return -1;
int fd = open(path, O_RDONLY);
if (fd < 0) return -1;
int n = read(fd, buf, sz);
if (n < 0) {
close(fd);
return -1;
}
int r = process(buf, n);
if (r < 0) {
close(fd);
return -1;
}
int fd = open(path, O_RDONLY)
orelse return -1;
defer close(fd);
int n = read(fd, buf, sz)
orelse return -1;
return process(buf, n);
Node *n = find(tree, key);
if (n == NULL) return NULL;
Node *child = n->left;
if (child == NULL) return NULL;
return child->value;
Node *n = find(tree, key)
orelse return NULL;
return (n->left orelse return NULL)->value;
Inline error handling without the noise.
Checks for NULL or negative return values inline. No nested if-blocks, no repeated cleanup calls, no diverging error paths.
No garbage values. Ever.
Every local variable and struct field is automatically zeroed at declaration. The class of bugs from uninitialized memory is simply gone.
typedef struct {
int width;
int height;
int flags; // garbage
} Config;
Config cfg; // uninitialized
if (cfg.flags & VERBOSE) {
// undefined behavior
}
typedef struct {
int width;
int height;
int flags;
} Config;
Config cfg; // zeroed automatically
if (cfg.flags & VERBOSE) {
// always safe — flags == 0
}
char *result; // dangling garbage
if (condition) {
result = compute();
}
use(result); // crash if !condition
char *result; // NULL automatically
if (condition) {
result = compute();
}
use(result); // NULL check handles it
int x = 10;
{
int x = 20; // silently shadows outer x
process(x); // uses 20, not 10
}
// gcc/clang: no warning by default
int x = 10;
{
int x = 20;
// error: 'x' shadows outer variable
// declared at line 1.
// Rename or use outer 'x' directly.
}
int a = 2147483647;
int b = a + 1;
// undefined behavior — wraps to
// -2147483648 on most platforms
write_size(b); // corrupted value
int a = 2147483647;
int b = a + 1;
// error: signed integer overflow
// is undefined behavior.
// Use checked_add() or cast to int64.
int *p = malloc(sizeof(int));
// void* silently converts to int*
// type information is lost
// wrong sizeof goes undetected
int *p = (int*)malloc(sizeof(int));
// explicit cast required — or:
int *p = alloc(int);
// typed, correct sizeof, inferred
Patterns C allows.
Prism doesn't.
Variable shadowing, signed overflow, implicit void* casts — legal C that's almost always a bug. Prism makes them compile errors, not surprises in production.
Verified against OpenSSL, SQLite, Bash, Curl, GNU Coreutils, and Make — unmodified source.
View Prism on GitHub