Post-POSIX Manifest
Every generation since the 1970s has recognized that the UNIX/POSIX API is clumsy, unsafe, and full of historical artifacts — yet instead of fixing it, we’ve just kept wrapping it.
Abstractions like GLib, Qt, Boost, libc++, .NET, and now modern Rust and Zig standard libraries all end up carrying the same baggage: open()
, read()
, write()
, close()
, errno
.
They’re “portable,” but only because everyone agreed to keep repeating the same ancient mistakes.
Why deprecation is the only cure
The only sane path forward is deprecation with replacement, not abstraction.
But that requires what UNIX culture has always avoided — breaking change.
If we could start fresh:
- Replace
int fd
with opaque handles (strong typing). - Replace
errno
with result objects. - Replace magic bitmasks with structured options.
- Replace “everything is a file” with “everything is a capability.”
- Drop implicit blocking behavior, and make async first-class.
- Make paths UTF-8 everywhere and reject invalid encodings.
- Make I/O functions return rich error types, not
-1
.
The real barrier
The problem isn’t technical — it’s sociological. POSIX compliance is treated as a religion, not a design choice. Nobody dares remove it because “legacy compatibility” pays the bills.
So we get more wrappers, bindings, and sandboxes — all orbiting around a fossilized center.
The bigger picture: a Post-POSIX OS model
- Objects instead of files
- Everything you open returns a typed handle (e.g.,
Socket
,File
,Process
).
- Everything you open returns a typed handle (e.g.,
- Capabilities replace global namespaces
- No implicit
/dev
,/proc
, or current directory; all access is explicit.
- No implicit
- Async I/O by default
- Non-blocking ops, unified completion model.
- Structured error taxonomy
- Cross-language portable error model (
AccessDenied
,NotFound
,Timeout
, etc.)
- Cross-language portable error model (
- UTF-8 everywhere
- Strings are Unicode by default, no locale chaos.
- Flat handle space
- 64-bit opaque handles managed by the kernel, not process-local
int
s.
- 64-bit opaque handles managed by the kernel, not process-local
Why do we need a new language
Most people assume the only two ways forward are:
“Stay in C and suffer forever” or “rewrite everything in Rust or Zig.”
But that’s a false dichotomy — neither of those languages solves the philosophical rot that started with UNIX and C. They both inherited modernized symptoms of the same disease.
Rust gave us safety through bureaucracy — it solves memory corruption, not conceptual poverty. Zig gave us clarity through minimalism — but its design still assumes that the UNIX API model is sacred truth.
Both reinforce the same old assumptions:
That system code must be hand-written, imperative, explicit, and file-descriptor-oriented.
The difference is philosophical: C and Rust describe how, a true post-POSIX language should describe what.
What’s really missing
What we actually need is a semantic systems language, not just a “better C.” What we need isn’t a new language syntax. It’s a new semantic layer for reality — something that treats the OS as a runtime of capabilities, not a bag of descriptors.
That means a language whose core abstractions reflect capabilities, processes, communication, and intent, not pointers
, fopen()
, and errno
.
For example:
- Handles are typed capabilities, not integers or references.
- Errors are part of the type system, not exceptions or implicit globals.
- I/O and concurrency are structured (think “transaction blocks” instead of
select()
loops). - Memory safety is orthogonal to resource safety — you can have both without ownership algebra.
- The standard library defines a semantic model of computation, not a wrapper around syscalls.