Memory-Safe C in the Real World: Fil-C, Rust, and the Future of Legacy Code
For decades we’ve treated memory safety bugs in C as an uncomfortable fact of life: dangerous, expensive, but somehow unavoidable if you want performance and control. Now tools like Fil-C are forcing a more interesting question: what if we could harden existing C codebases—sudo, game engines, network daemons—without throwing everything away and starting over in Rust? Fil-C’s fat pointers, runtime checks, and garbage-collected semantics don’t magically turn C into a modern high-level language, but they do open an intriguing middle path between “ship and pray” on one side and “rewrite the world” on the other. The appeal is obvious: keep the battle-tested logic, bolt on strong runtime protections against memory corruption, and accept some overhead in exchange for fewer catastrophic bugs. It’s not a silver bullet, and it’s absolutely not free, but it hints at a future where memory safety isn’t limited only to greenfield projects and heroically ambitious rewrites.
Take a look at @ThePrimeTimeagen’s original video:
Short version: the video uses Fil-C as a case study in “memory-safe C” and ends up arguing that for big, existing C codebases (sudo, binutils, editors, etc.), tools like Fil-C are often a saner path than “rewrite it all in Rust,” but they’re not a universal solution, especially for performance-critical stuff like game engines.
What the Fil-C Video Covers
1. What Fil-C actually is
- It’s a C toolchain/runtime that makes pointers “fat” at the implementation level: each pointer carries hidden metadata (lower bound, upper bound, etc.), but from the C programmer’s perspective it still looks like a raw pointer. They even have a special
%Pformatter to print out the pointer’s bounds. - Every pointer dereference is instrumented with runtime checks against those bounds. If you index past the buffer (e.g., access byte 42 in a 16-byte allocation), Fil-C traps the program instead of letting you wander into UB.
- It ships with a garbage-collected runtime:
free()doesn’t actually free; it marks memory as reclaimable, and a background, multi-threaded GC reclaims it later. So “normal” C semantics aroundmalloc/freearen’t quite what they look like anymore.
They stress that this is not “standard C with a library” — this is the default experience for Fil-C: C syntax, but every pointer access is checked and memory is GC’d.
2. Performance and overhead
Prime quotes concrete numbers from his experiments: Fil-C’s runtime impact is somewhere around 1.2x to 4x slower than plain C on the same code, depending on workload.
They also call out:
- Extra memory usage because pointers carry metadata and the runtime keeps bookkeeping structures.
- The overhead is always there in production; this isn’t like compiling with ASan only in debug builds.
TJ (playing “Rust guy”) hammers the point that this is not a zero-cost abstraction: you pay in CPU and RAM to get these safety checks.
3. Why this came up: sudo, CVEs, and rewrites
Ed (low-level security) uses sudo as the motivating example:
- Sudo is a setuid-root binary, widely deployed, heavily audited, and a huge target for memory-corruption research.
- There’s been a push to rewrite sudo and other binutils/coreutils in Rust (sudo-rs and friends). But the Rust sudo rewrite still ended up with multiple CVEs — not memory corruption, but logic bugs introduced during the rewrite.
The big point: changing language doesn’t preserve behavior. Rewriting a security-critical C program in Rust:
- Fixes a class of memory bugs.
- But creates a brand-new codebase with brand-new logic bugs, and you cannot realistically guarantee “bug-for-bug compatibility” with the old version.
So Ed’s argument is basically:
Instead of rewriting everything in $NEW_LANG every 20 years, why not drop in a new compiler/runtime that adds strong memory checks on the existing, battle-tested C code?
That’s where Fil-C fits: same source, same logic, but instrumented memory.
4. Fil-C vs Rust vs sanitizers
They contrast Fil-C with two things:
-
AddressSanitizer / UBSan
- Great for testing, but not usually something you ship with; the overheads are huge and impact layout/ABI.
- They don’t change the language; they’re debug-time tools. Fil-C is framed as something you could plausibly run in production for certain classes of software, albeit slower.
-
Rust
- Rust’s type system and borrow checker prevent a lot of memory errors statically, but not all: you can still have runtime panics that turn into denial-of-service conditions in privileged code.
- There are also resource types (file handles, etc.) that neither Fil-C nor Rust make magically “safe” out of the box; Fil-C is focused on memory, not on all lifetime/resource issues.
- Casey points out that both Fil-C and Rust can have bugs in their own memory-safety implementations; Rust doesn’t get a free pass there.
There’s even a section comparing Fil-C’s software approach to hardware memory tagging (using the high bits of pointers as tags and checking them against tagged memory regions on access), which is conceptually similar but implemented in silicon instead of the compiler/runtime.
5. Where would you actually use this?
Security-sensitive userland (sudo, editors, services)
Consensus: Fil-C is a pretty reasonable tool for stuff like:
- Sudo, other suid binaries.
- Network-facing daemons.
- Editors or services that load plugins or untrusted content.
Because:
- Performance overhead of 1.2—4x is often acceptable.
- You keep the existing C logic that’s been beaten on for decades.
- You add runtime enforcement against memory bugs that still lurk in corners.
Game engines and high-perf workloads
Prime asks Casey directly whether he’d use Fil-C in a game engine. Casey’s answer is basically “mostly no, occasionally yes”:
- For a typical single-player / isolated game, he doesn’t see much need; you’re on a client box, attack surface is smaller, and performance matters a lot.
- But for platforms like Roblox/Fortnite, where users upload content and scripts and you’re constantly ingesting untrusted input, he can imagine using Fil-C for specific subsystems that process that untrusted data, accepting the overhead there for extra safety.
So Fil-C isn’t pitched as “rewrite your whole engine with this.” It’s more: maybe you wrap the dangerous choke points in a slower but safer runtime.
My take
Bluntly:
- Fil-C is a clever, niche solution, not a new default for C.
- “Rewrite it all in Rust” is also over-sold.
A few more concrete points.
1. For legacy C, Fil-C is one of the few realistic options
If you’ve got 500k—5M lines of C that’s been shipping for 20+ years, the panel is right: a wholesale Rust rewrite is usually fantasy. You don’t have the time, and you will absolutely ship new logic bugs along the way.
For that situation, a tool like Fil-C (or any compiler+runtime that:
- instruments pointer use,
- enforces bounds and use-after-free at runtime,
- and can be turned on for parts of the system,
is genuinely attractive. You get incremental hardening instead of a moonshot rewrite. That’s the strongest, most defensible argument in the video, and I think they’re right to lean on it.
2. GC-C is fine for some domains and wrong for others
Fil-C’s GC is both its selling point and a huge foot-gun:
- For sudo, binutils, and most command-line tools, a concurrent GC that occasionally wakes up is totally fine. Latency isn’t hyper-critical, and the codebase already “thinks” in malloc/free terms anyway.
- For real-time, hard-latency systems (games, audio, trading, kernels), sneaking in a GC under your
free()calls is a non-starter. You’ve just changed the timing model of the code in a way that’s extremely hard to reason about.
So as a general systems language story, I wouldn’t sell “GC-C” as the future. As a hardening tool for the huge pile of non-real-time C out there, it’s a lot more reasonable.
3. Rust vs Fil-C isn’t a moral question; it’s a cost model
The video has some light Rust-baiting for entertainment, but strip that away and the trade-off is:
-
Rust:
- Better static guarantees and ergonomics for new code.
- Worse story for huge legacy C piles (rewrites are expensive and dangerous).
- Great when you’re designing a new system or heavily refactoring anyway.
-
Fil-C:
- Awkward ergonomics (still C + GC surprises) but great drop-in for existing C.
- Safety is purely runtime; you can still write stupid code, but it crashes instead of giving RCE.
- Better when the primary constraint is “we must not break behavior, but we want fewer memory exploits.”
If I were starting a new privileged daemon from scratch in 2025, I’d still probably pick Rust or another modern memory-safe language, not Fil-C, because developer time and maintainability matter as much as raw safety. But if I owned sudo itself? I’d absolutely consider something like Fil-C (or future CHERI-like hardware) as a first step before signing up for a multi-year rewrite.
4. For games, I’d mostly stick with the current pattern
For your world (engines, tools, scripting, plugins), I think:
- Core engine: C/C++ (or Rust/Zig/etc.) with a mix of strict coding standards, fuzzing, and sanitizers in debug; Fil-C here feels like too much tax for too little gain.
- Untrusted content / scripting boundary: either a sandboxed VM (Lua, WASM, etc.) or, in some corner cases, Fil-C-style hardening around the parsers/serialization code if those are performance-tolerant.
So Casey’s “maybe in tightly scoped, user-input-heavy parts, but not the whole engine” reads as the right answer.
Memory Safe Programming Languages
What are some memory safe programming languages? Short answer: anything that doesn’t let you casually scribble over raw memory tends to count. In practice there are a bunch of mainstream “memory-safe by default” languages.
Here are the big buckets:
-
Managed, GC’d languages (memory safe unless you use native FFI):
- Java, Kotlin
- C# / F#
- Go
- Python
- JavaScript / TypeScript
- Ruby, PHP
- Clojure, Scala, Elixir, etc.
These all use automatic memory management and only give you objects, arrays, and references—not raw pointers. Out-of-bounds and use-after-free are prevented by the runtime (you may get exceptions or panics, but not silent memory corruption).
-
Statically typed, functional-ish, safe by design:
- Haskell
- OCaml / Reason / ReScript
- Elm
- Futhark, etc.
Same story: no raw pointer arithmetic in normal code, GC or region-based memory, strong type systems. Memory bugs are extremely rare outside of FFI.
-
Systems languages with explicit safety models:
- Rust — the poster child. The borrow checker + ownership rules prevent use-after-free, double free, and data races in “safe” Rust. There is
unsafeas an escape hatch, but normal Rust code is designed to be memory safe. - Swift — ARC, bounds-checked arrays, strong typing. It has unsafe APIs you can opt into, but ordinary Swift code is memory safe.
- Zig — not memory-safe by default like Rust, but it has safety features (bounds checks, use-after-free detection in debug modes) you can keep turned on; worth mentioning as “safer C”, not truly memory-safe.
- Rust — the poster child. The borrow checker + ownership rules prevent use-after-free, double free, and data races in “safe” Rust. There is
-
Specialized / research or niche languages focused on safety:
- Ada/SPARK
- Cyclone (historical, C-like safe language)
- Pony, Rust-inspired research languages, various ML-family offshoots
Important nuance:
- “Memory safe” doesn’t mean “bug free.” You can still have logic bugs, panics, infinite loops, etc. It specifically means you don’t get undefined behavior from accessing or freeing memory incorrectly in the safe subset of the language.
- Many of these languages let you drop into unsafe or FFI blocks. Once you’re there, all bets are off—you’re back in C-land.
If you tell me what domain you care about (systems, web backend, games, embedded), I can narrow this down to the few languages that make the most sense for you to actually invest in.
Conclusion
Fil-C won’t replace Rust, and it won’t redeem every gnarly C codebase that should probably be retired, but it does prove something important: memory safety for legacy systems is not a lost cause. The trade-offs are real—higher CPU and RAM overhead, a garbage collector that changes timing characteristics, and safety that’s enforced at runtime rather than in the type system—but for the right targets, especially security-sensitive userland like sudo and network services, that price looks increasingly reasonable. In that sense, Fil-C feels less like a quirky experiment and more like a bridge technology: a way to carry critical C software into a safer era while the ecosystem slowly shifts toward languages with stronger guarantees. If we take it seriously, we don’t have to choose between “unsafe but fast” C and “rewrite everything” Rust evangelism. We can mix approaches, harden what we have, and reserve rewrites for when they actually pay off. That’s not the most glamorous vision of the future—but it might be the one that lets our software, and our users, survive long enough to see it.