Fil-C: Garbage In, Memory Safety Out [video]
Posted by Bootvis 1 day ago
Comments
Comment by dang 6 hours ago
Memory Safe Context Switching - https://news.ycombinator.com/item?id=48727177 - June 2026 (30 comments)
Memory Safe Inline Assembly - https://news.ycombinator.com/item?id=48606096 - June 2026 (47 comments)
The Fil-C Optimized Calling Convention - https://news.ycombinator.com/item?id=48162876 - May 2026 (32 comments)
A simplified model of Fil-C - https://news.ycombinator.com/item?id=47810872 - April 2026 (136 comments)
Pizlix: Memory Safe Linux from Scratch - https://news.ycombinator.com/item?id=46260852 - Dec 2025 (30 comments)
Linux Sandboxes and Fil-C - https://news.ycombinator.com/item?id=46259064 - Dec 2025 (156 comments)
Ported freetype, fontconfig, harfbuzz, and graphite to Fil-C - https://news.ycombinator.com/item?id=46090009 - Nov 2025 (56 comments)
A Note on Fil-C - https://news.ycombinator.com/item?id=45842494 - Nov 2025 (210 comments)
Fil-C: A memory-safe C implementation - https://news.ycombinator.com/item?id=45842242 - Nov 2025 (1 comment)
Notes by djb on using Fil-C - https://news.ycombinator.com/item?id=45788040 - Nov 2025 (246 comments)
for more, goto https://news.ycombinator.com/item?id=45792588
Comment by wasmperson 1 day ago
Someone in the audience also pointed out that a tool like this could be used to compile rust programs, not just C programs, in which case it's odd to hear Fil-C repeatedly framed as a language in opposition to rust rather than as a tool which might complement it.
Comment by modeless 6 hours ago
Adding Fil-C-like runtime checks to Rust is definitely an interesting direction.
Comment by wasmperson 5 hours ago
That's the thing, though. Your Fil-C code isn't calling mmap, it's calling the Fil-C wrapper for mmap. In neither Fil-C nor Rust('s safe subset) can you call the actual system mmap.
Comment by modeless 5 hours ago
Comment by Cyph0n 4 hours ago
mmap is a bit of an outlier because it is not possible to implement a fully featured safe wrapper (MAP_SHARED) in Rust. So I would be curious to see what safety guarantees Fil-C claims to provide for mmap.
Comment by deathanatos 3 hours ago
You might call that library "nix"[1]. Many, though not all, of the bindings are safe wrappers around the underlying unsafe syscall.
(The specific call of mmap from upthread, though, that one is not. I'm not sure how you would make such a call safe.)
Comment by jitl 4 hours ago
Comment by MaulingMonkey 3 hours ago
Just write `#![forbid(unsafe_code)]` at the top of your src/lib.rs, and track crates not using it with `cargo geiger`. You don't need a whole new language, it already provides the tools to wield that hatch shut.
> Rust is less safe because it has a feature for turning off safety
Can I write Fil-C's mmap wrapper in Fil-C?
If no, fair enough, but it's worth noting I can write a safe Rust mmap wrapper in Rust, and I absolutely and frequently need to do that kind of "syscall wrapping" in arenas Fil-C explicitly hasn't handled, by virtue of being explicitly a Linux project.
If yes, that sounds like an escape hatch to Fil-C's memory safety, undermining the claim that Fil-C is fundamentally safer, and you're now at best arguing it's safer as tends to be used.
Comment by Cyph0n 3 hours ago
So I don’t see how Rust is at a huge disadvantage here when Fil-C is typically not going to be enforcing safety in production. I suppose the testing/fuzzing story becomes all the more important because you need to exercise runtime behavior for Fil-C to detect unsafe faults.
Don’t get me wrong: I think Fil-C is a smart idea and an excellent way to introduce memory safety to existing C codebases. But I don’t get this whole (usually implied) idea that Fil-C somehow makes Rust look bad or renders it obsolete.
Comment by baranul 2 hours ago
Comment by modeless 3 hours ago
Comment by josephg 3 hours ago
C is more verbose and more error prone than Go and C#. It has worse tooling. It’s missing decades of language features. C isn’t properly cross platform. There’s no package manager. The “standard library” isn’t fully standard. It’s full of sharp edges and bad decisions.
I can imagine using Fil-C to run legacy code. But for new projects, it just seems worse in every way compared to Go, C#, typescript and friends. It’s worse, slow, and inconvenient.
Comment by baranul 2 hours ago
But what is being overlooked, is the massive numerical dominance of C programmers and projects, along with legacy and embedded code. There is going to be a preference for writing and using C, that could arguably fuel Fil-C for a very long time.
Comment by josephg 2 hours ago
I roll to doubt. I haven’t seen C topping programming language popularity charts for a long, long time.
I used to interview software engineering candidates professionally. Candidates could pick any language they wanted for the interview. Python was chosen by about 70% of our interviewees. C was under 5%. (N=400 or so)
Comment by baranul 1 hour ago
C is still highly ranked on many charts. C (as of July 2026) is ranked #2 on the TIOBE index. PYPL has C and C++ weirdly merged, the annoying C/C++, and that comes in as #3. The IEEE and Redmonk have C firmly in the top 10, using different methodologies. We also have to consider how many years back that C's run in the top 10 goes.
We have to also use context, as popularity can be a measure beyond simply job demand, relative to usage by students, hobbyists, open-source, and legacy. Furthermore, demand can be relative to location and country. Because it is less in your specific area or company, doesn't mean that's so in other areas or countries.
Comment by rcxdude 36 minutes ago
Comment by Cyph0n 3 hours ago
Also, it’s probably fast enough for apps to be deployed in something like a staging environment for integration tests to run against them. Production deploy/release can then happen using a tagged version that gets compiled using vanilla GCC/Clang.
Comment by modeless 3 hours ago
Comment by modeless 4 hours ago
Comment by wildfireday2 3 hours ago
Even so Fil-C fails at being 100% compatible for userspace due to the silly things people do with pointers. Hence the large amount of effort he’s had to do to fix up that 0.1% of userspace that breaks.
Comment by minraws 5 hours ago
Comment by quotemstr 5 hours ago
Comment by jcranmer 5 hours ago
Comment by modeless 4 hours ago
Of course I'm kidding, but it is absolutely the case that if you truly care about safety you need to consider more than the program source code and binary, but also the environment, including kernel and hardware. The user doesn't care if their web browser got hacked via stack buffer underflow or /dev/exynos-mem or Rowhammer; the result is the same.
Comment by pizlonator 4 hours ago
Point is, Fil-C goes further than any other memory safety tech in terms of what it guards
Comment by modeless 3 hours ago
Comment by josephg 3 hours ago
Comment by pizlonator 2 hours ago
So Fil-C is safer than those
Comment by josephg 2 hours ago
> Fil-C is safer than those
Says you. It seems presumptuous to me to be so dismissive of their work. The Go and C# teams do good work.
But I wasn’t even asking about safety. I’m curious about ergonomics and performance. Fil-C isn’t the only safe wrapper around raw syscalls. How is cross OS compatibility with Fil-C? How nice are the APIs to use? UNIX syscalls are pretty badly designed imo. The error paths alone are a mess.
Comment by Splizard 2 hours ago
Comment by pizlonator 1 hour ago
Go’s situation is nuanced since a lot of Go code does rely on unsafe C or C++ deps, but I have no idea how generally true that is. Also Go’s protections fall apart under certain races, which isn’t true in Fil-C.
Comment by quotemstr 54 minutes ago
Fil-C's safety guarantees therefore fail to apply in this situation.
Comment by ncruces 2 hours ago
Comment by quotemstr 1 hour ago
Anyone could do the same with a Rust lint rule if he wanted. He wouldn't, because this policy is not the way to secure software.
Comment by pizlonator 4 hours ago
“Custom” and “system” aren’t the terms I use; I say user libc and you libc (because they are basically the same libc - either both are musl or both are glibc).
User libc calls to the Fil-C runtime, which filters syscalls, and those filtered syscalls are made via yolo libc.
Hence, a Fil-C program is memory safe down to the syscalls and syscalls cannot be used to escape the protections (unless you do weird stuff with /proc)
Comment by wasmperson 3 hours ago
Comment by flumpcakes 3 hours ago
Comment by josephg 3 hours ago
Fil-C: Combining the ergonomics of C with the performance of Python!
Comment by mort96 2 hours ago
If I'm understanding correctly, it's this general syscall function that's safe in Fil-C.
Comment by cypherpunk666 7 hours ago
fil-c runs on linux.
what if linux ran on fil-c?
Comment by josephg 2 hours ago
Comment by wavemode 1 hour ago
Comment by josephg 1 hour ago
It would sometimes be a good trade off, for some users. But I don’t think many regular users would choose to pay this cost.
Comment by yjftsjthsd-h 5 hours ago
Comment by doctorpangloss 5 hours ago
Comment by cypherpunk666 7 hours ago
Comment by FuckButtons 5 hours ago
Comment by josephg 2 hours ago
Comment by LoganDark 7 hours ago
You can combine both approaches for sure, but that doesn't change that they are indeed separate approaches. One (Rust) intends to characterize and prevent undefined behavior at compile time, and another (Fil-C) intends to make undefined behavior impossible at runtime, as evidenced by decisions like (for example) making unsafe usage of setjmp/longjmp safe.
You could sort of achieve "safety-in-depth" by combining both approaches, but Rust prefers to prevent all undefined behavior statically, and Fil-C prefers to focus on dynamic approaches. They have real differences, so that's probably what comes off as "oppositional"
Comment by raggi 7 hours ago
Comment by LoganDark 6 hours ago
Comment by uecker 6 hours ago
Comment by woodruffw 6 hours ago
(I'm separately skeptical that it's the most important memory safety property; I suspect that a review of Chrome and Firefox 0days would show that UAFs and type confusion are, at least to attackers, equally if not more important.)
Comment by zem 5 hours ago
Comment by ratmice 5 hours ago
Comment by LoganDark 5 hours ago
Comment by ratmice 4 hours ago
Comment by zem 4 hours ago
Comment by uecker 4 hours ago
Comment by woodruffw 4 hours ago
Where? The GP's comment says "prefers," which I didn't read to mean a blanket statement. I don't think anybody with more than passing experience in Rust (or C) would make such a claim.
> Some dependently types languages can prove this statically, also model checking can do this, etc. So it can be done statically as well, but not in Rust.
You're couching the part where it can't be done with full generality or can be done with full generality, but with punishing semantics. The appropriate comparison here is with other normal general purpose compiled languages.
Comment by uecker 4 hours ago
Dependently types languages and model checking do exist. They come with tradeoffs, but this is also true for Rust.
Comment by LoganDark 6 hours ago
Comment by woodruffw 6 hours ago
(You mentioned WUFFS below, which is why I qualified with general-purpose! One thing that WUFFS does that I think Rust could add pretty easily is provable indexing, e.g. allow me to use a `u8` to index a `[u8; 256]` without having to widen to a `usize` first and hope that LLVM optimizes it back out.)
Comment by haberman 5 hours ago
My thought here is to proactively verify that LLVM elided the automatic bounds checks in places where you believe that your explicit checks should be sufficient.
That was a key part of my article on "No-Panic Rust": https://blog.reverberate.org/2025/02/03/no-panic-rust.html ("A Dance With The Optimizer")
Comment by uecker 4 hours ago
Comment by LoganDark 4 hours ago
Comment by uecker 4 hours ago
Comment by wasmperson 5 hours ago
for i in some_arr {
println!("{i}");
}
I suspect most techniques you could think of to statically avoid bounds checks are possible in rust's type system.Comment by uecker 4 hours ago
Comment by wasmperson 3 hours ago
Comment by uecker 3 hours ago
fn main() {
let v = vec![1, 2, 3];
v[5];
}Comment by steveklabnik 3 hours ago
However, a vector's length is inherently a runtime concept, and is a user-defined library type, so the compiler does not attempt to directly reason about this at compile time. However, for this example, post-optimizations, it doesn't do a runtime check at all: it calls the panic directly, because the optimizer does in fact reason that this always panics and removes the dynamic check.
It is true that Rust inserts dynamic checks for things that it can't prove statically, but so do dependently typed languages. "Please read an integer from stdin and then load that element of an array" is not possible to statically check, it must rely on runtime checks, definitionally.
Comment by uecker 3 hours ago
me: It does dynamic bounds checking.
Rustaceans: But... for XYZ ... it doesn't...
Sigh.
Comment by josephg 2 hours ago
Rust does prefer static checks. It uses static analysis to prevent many types of bugs - including use-after-free and data races. But you’re right; rust still falls back to runtime checks when static analysis is too hard. Like dynamic bounds checking and integer overflow (in debug mode). Cell, Refcell and friends also have a (small) runtime cost. Rust prefers static analysis but does not use it exclusively.
Let’s say rust is 80% static analysis, 20% dynamic checks. Fil-C seems to go the other way and have 80+% dynamic checks. It’s an interesting point.
Nobody disagrees with you that rust sometimes inserts dynamic bounds checks. But “prefer” in this context means “most of the time, when possible” not “all of the time”. I prefer vanilla ice cream over chocolate. But I still eat chocolate ice cream when vanilla isn’t available. Pointing out that one time I ate chocolate ice cream isn’t a gotcha moment.
Comment by steveklabnik 2 hours ago
Rust (outside of the unsafe superset) has no undefined behavior. That is guaranteed, statically, at compile time: all execution paths have well-defined behaviors. That does not mean that there are no runtime consequences whatsoever. For example, the behavior of an invalid index is a panic, at runtime. Inserting a check for an invalid index at compile time is static enforcement of the behavior, even if the check itself happens at runtime.
If you had said "sometimes, Rust can't eliminate dynamic checks because it doesn't have a strong enough reasoning about some compile time properties" nobody would be arguing with you about this. Your example is one where a language with stronger reasoning would be able to detect it, for sure. Lean can do this, for example.
Comment by wasmperson 2 hours ago
Again: I'm not disputing the fact that the rust index operator does a dynamic bounds check.
Comment by LoganDark 6 hours ago
Comment by n42 7 hours ago
Comment by LoganDark 6 hours ago
Comment by quotemstr 7 hours ago
Comment by pizlonator 4 hours ago
Comment by tialaramex 3 hours ago
I gave (somewhat arbitrarily) the example that safe Rust is OK with a 64-bit pointer (on a modern PC for example) having the value made by the UTF-8 text "LAUGHING". That's 8 bytes, 8 bytes is 64 bits, it fits perfectly. Of course I point out, unsafe Rust, which is allowed to dereference pointers, must never dereference this LAUGHING pointer, it's not actually pointing at anything, but it is allowed to exist in safe Rust.
Coincidentally, in like February or so this year, a new third-party Rust type "ColdString" was introduced to their reddit by somebody I've never met - I read about it and encouraged its author. ColdString is 64 bits (on modern hardware) because inside it's a pointer, and indeed if the text you wanted to store in a ColdString was "LAUGHING" you do that exactly as I described in the December post - it's just the UTF-8 encoded text, no problem. The clever trick inside ColdString is how it can know when that 64-bit pointer really is actually a pointer for longer strings - yet also allows any 0 to 8 byte UTF-8 text string to just be encoded in the ColdString itself directly.
Comment by LugosFergus 6 hours ago
Comment by quotemstr 5 hours ago
Rust, for all its faults, at least engages with its critics. I've long faulted Rust's use of Result over exceptions, for example, but the maintainers at least acknowledge that other options exist and each has trade-offs. Not Pizlo and his fans.
To be clear, I like Fil-C. It's a practical implementation of something that should have existed a long time ago. And Pizlo is, in fact, a great programmer. It just rubs me the wrong way that he can't be content with having done excellent work --- he has to claim things that his system doesn't provide (like memory safety under data races --- minor fault, but still) and claim that other systems are worse in ways they are not (e.g. with respect to Rust having unsafe blocks).
Is genuine excellence not enough? Why must he persist in claiming a false perfection?
Comment by dundarious 4 hours ago
If considering only "safety", then Fil-C is more safe than any Rust containing unsafe blocks, no? With the usual caveats about whether an abort() is safe.
Comment by NobodyNada 3 hours ago
With Fil-C, the "unsafe blocks" live entirely within the compiler and runtime. With Rust, the unsafe foundation is the Rust compiler and standard library, as well as any unsafe code within your application or dependencies.
So either way you're in the same situation of relying on the correctness of the unsafe code you depend on. But there are two major differences:
- Unsafe code can be written in Rust, instead of inside the compiler. This is much easier to write and to review for correctness.
- People other than Fil are allowed to write unsafe Rust. This is what Fil's point is about, and yes, it allows you to opt-in to increasing your attack surface by trusting unsafe code written by yourself or your dependents. Rust allows the user to choose where they draw the trust boundary, and Fil-C does not.
It's true that Fil is probably better than I am at writing unsafe code. So "Fil-C is safer than Rust" is true in that sense. But Fil-C is certainly not safer than safe Rust. And sure, you can't run all the Rust code in the world if you compile with '--deny unsafe_code'; but Fil-C can't run all the C code in the world either. Unsafe Rust is rarely needed, mostly only if you want to do pointer crimes or FFI, and Fil-C doesn't support a lot of (perfectly legal) pointer crimes and FFI either.
Comment by flumpcakes 3 hours ago
Comment by NobodyNada 3 hours ago
Comment by quotemstr 1 hour ago
Comment by mshroyer 3 hours ago
Comment by pizlonator 4 hours ago
Comment by NobodyNada 3 hours ago
There are roughly two categories of Rust user:
1. People who are using Rust for application development. Most of these users write zero unsafe blocks in their careers. For these users, "memory safety" was probably not a strong reason to pick Rust, because there are a wealth of other memory-safe application development languages out there.
2. People who are using Rust for systems programming (i.e. programming under resource or environment constraints). These users may write unsafe for performance reasons, or to do things like hardware MMIO; and they're using Rust over C/C++ either for security or just because the tooling is nicer.
The first category of user is unlikely to consider C for application development in this decade; they're going to be comparing Rust against Go or Java or Node.js. Fil-C solves the security problem of memory safety, but it does not free the developer from the difficulty of having to manually write memory-safe C code; their program will just crash if they get it wrong.
The second category of user cannot use Fil-C because of its performance overhead, runtime requirements and/or lack of escape hatches for MMIO/FFI.
Where Fil-C does shine is for legacy application software written in C. Here, it's a free lunch: a way to harden the massive amount of existing software without an expensive rewrite. I would love to see distros shipping pizlonated coreutils, ffmpeg, systemd, curl, sudo, postgres, etc., anything that has a big attack surface, but does not need to be memory-unsafe.
This is a problem I care about a lot, and your work here is truly a monumental advancement in the field. Comparisons to Rust sell it short by inviting endless debate on problems largely tangential to Fil-C.
Comment by pizlonator 2 hours ago
I have articulated the specific ways that Fil-C is better.
And I’ve articulated the specific ways that Rust is better. You’ve enumerated some of those reasons from your perspective, though I disagree on the details.
I like those kinds of conversations. We shouldn’t shy away from them as a community. They help us grow a shared understanding of the tech
Comment by uecker 4 hours ago
Comment by jagadaga 5 hours ago
Why?
Comment by AlotOfReading 4 hours ago
That said, I've spent too much of my life chasing implicit control flow to accept exceptions. Heck, C++'s "noexcept" is a strong argument against exceptions all by itself.
Comment by wasmperson 3 hours ago
https://smallcultfollowing.com/babysteps/blog/2024/05/02/unw...