He claims that all syscalls are safe because they're implemented by his custom libc, but that libc then calls out to the system libc, where the system calls are unsafe. He then claims that this is unlike rust, where making a syscall is unsafe. If you stick to the rust standard library in the same way that fil-c programs stick to the fil-c standard library, then all of your rust "system calls" are safe, too.
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.
modeless 1 days ago [-]
Rust doesn't runtime validate that your usage of syscalls is memory safe, while Fil-C does. For example you can call mmap in Fil-C and it is still guaranteed to be memory safe, while in Rust you can easily violate memory safety by calling mmap. This seems like an unambiguous improvement to me. This is as memory safe as it is possible to be on a system with a kernel that is not memory safe.
Adding Fil-C-like runtime checks to Rust is definitely an interesting direction.
wasmperson 1 days ago [-]
> For example you can call mmap in Fil-C and it is still guaranteed to be memory safe, while in Rust you can easily violate memory safety by calling mmap.
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.
modeless 1 days ago [-]
Fil-C has access to a memory safe API called mmap with a lot of the capabilities of the mmap system call, while Rust's safe subset does not. Rust allows you to use the mmap system call unsafely, while Fil-C does not. I feel these are both big advantages for Fil-C. You'll never have to audit a Fil-C codebase to determine whether its calls to mmap are memory safe, or indeed any other calls to any API or dependency, even those written in C.
Cyph0n 24 hours ago [-]
How is Fil-C’s wrapper approach different from someone (not necessarily the Rust stdlib) implementing a safe wrapper around a particular syscall?
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.
deathanatos 23 hours ago [-]
> from someone (not necessarily the Rust stdlib) implementing a safe wrapper around a particular syscall?
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.)
Rust is less safe because it has a feature for turning off safety, called `unsafe`. Fil-C does not have a way to turn off safety, and it enforces safety checks at runtime. That's the reasoning anyways. If you define another language Rust-Without-Any-Unsafe, then maybe that one is safer than Fil-C.
MaulingMonkey 23 hours ago [-]
> If you define another language Rust-Without-Any-Unsafe, then maybe that one is safer than Fil-C.
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.
im3w1l 11 hours ago [-]
It should be mentioned that rust's type system has a long-standing known bug compromising memory safety, as made famous by cve-rs. It's perhaps not a big issue for most people but if you are someone that wants the assurances of #![forbid(unsafe_code)] that should probably be on your radar.
kibwen 10 hours ago [-]
Certainly, we should build into our threat model the idea that the implementation of Rust will have bugs that compromise safety until proven otherwise (merely fixing that one aforementioned bug won't alter that calculus). And the same applies to Fil-C: we should assume it has bugs that compromise safety until proven otherwise. Until then, in practice, Rust has much more real-world use that convincingly demonstrates its ability to realistically eliminate memory safety risks.
im3w1l 10 hours ago [-]
A known bug with easily accessible proof-of-concept is quite different from a theoretical bug.
kibwen 9 hours ago [-]
No, that's not how this works. The bug being utilized by cve-rs has such roundabout requirements that it's unlikely that anyone has ever triggered it without trying (to be clear, the Rust devs plan to fix it regardless). Meanwhile, here's a memory safety bug that Fil-C fails to catch, not because of a compiler bug, but by design: https://www.reddit.com/r/cpp/comments/1v4nw0k/filc_garbage_i...
Cyph0n 23 hours ago [-]
But the runtime cost of Fil-C means that - in most cases - your actual production application will be compiled with a standard C compiler. This applies not just to your code, but to all dependencies (due to ABI).
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.
modeless 23 hours ago [-]
The whole point of Fil-C is that it is fast enough to consider using in production for some applications while still guaranteeing memory safety. We already have ASAN and Valgrind and other tools for development purposes, that's not what Fil-C is targeting.
josephg 22 hours ago [-]
So Fil-C is competing with Go, C#, typescript and Python. If I’m writing a greenfield project where “bare metal” performance isn’t needed, why would anyone choose Fil-C over a more mature GC language?
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.
baranul 22 hours ago [-]
Of the group of languages presented, your argument has more merit for Go. With C# and others, there is an additional OOP argument in there, that splits into different factions.
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.
josephg 21 hours ago [-]
> But what is being overlooked, is the massive numerical dominance of C programmers
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)
baranul 21 hours ago [-]
> I haven’t seen C topping programming language popularity charts
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.
Jach 10 hours ago [-]
A job market specific survey: https://www.devjobsscanner.com/blog/top-8-most-demanded-prog... They combine C and C++ together and it slides in at number 6. But together they only account for 5% of the job market. Totaling all such non-managed languages together is roughly 10% and that figure hasn't changed in decades. I'd bet that figure matches up with what consultancy firms that take on a wide variety of projects see. Needless to say, "massive numerical dominance" in relation to C is something that needs many qualifications to become true. Certainly there's a lot of C code out there, and C jobs, and in some niche domains it's very dominant. But there's just such a vast amount more of JavaScript, Java, and Python across more domains.
dminik 11 hours ago [-]
I'm sorry, but just mentioning TIOBE has killed any credibility you might have had. I mean, seriously, look at the list.
JavaScript at #6, when it should be at #1? Trailed by Visual Basic at #7? SQL (not a programming language) at #8 and R (have you ever seen an R job?) at #9. Then you have Rust just barely squeezing past Delphi? Followed by scratch???
Come on, this is not a serious source. Neither are the the rest really. Any popularity/usage list that doesn't have JS at first place is not serious.
pjmlp 14 hours ago [-]
Speaking for myself, I am never hired because I know C or C++, rather specific managed languages, however if there is the need to look under the runtime hood, or have bindings, like most Python libraries actually are, then suddenly it is welcome that I know C and C++ versus most folks on the team.
josephg 10 hours ago [-]
> suddenly it is welcome that I know C and C++ versus most folks on the team.
Yeah I know the feeling. But you don’t need Fil-C to learn C.
That said, with a GC, you could make some great tools & visualisations for learning C and understanding the memory layout of a running C program. That would be cool to see
rcxdude 20 hours ago [-]
to be fair, most programmers are at least a little bit polyglot, and if you know python and C and you're trying to solve an interview problem, python is gonna be the easier option 99% of the time, even if you're not that great at python compared to C.
josephg 10 hours ago [-]
> python is gonna be the easier option 99% of the time
If python/go/C# is easier to write, why choose C? Fil-C removes C’s performance and interoperability advantages.
rcxdude 9 hours ago [-]
Python is easier to write for interview questions. It generally doesn't scale well (in terms of size of team and codebase).
TBH C# is probably a good default for a lot of projects, from a safety and performance standpoint (rust's big performance win over GC/JIT languages is more in the memory consumption than throughput, counterintuitively). Fil-C probably still has a pretty good edge on python's performance, though.
cma 13 hours ago [-]
You could use raw C in environments with effective sandboxes, Fil-C in environments without a sandbox.
Or you could write a library that works for Fil-C and C without writing the library twice.
Cyph0n 23 hours ago [-]
I am assuming that Fil-C introduces orders of magnitude less overhead than ASAN or Valgrind.
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.
modeless 23 hours ago [-]
I'm not sure about the speed of ASAN, it may be comparable but doesn't guarantee memory safety. It's only for catching mistakes and not secure against an adversary. Valgrind is dramatically slower.
baranul 22 hours ago [-]
I think it's not so much that Fil-C renders Rust completely useless, but rather it reduces the supposed validity and wild arguments to frivolously rewrite anything and everything in Rust.
modeless 24 hours ago [-]
Adding Fil-C-like runtime checks to Rust is definitely an interesting direction. As I mentioned upthread. It's not just the availability of the safe API that's interesting, though, but also the prohibition on using the unsafe API in the entire program and all dependencies. Which Rust could also do in theory but not yet in practice AFAIK.
scns 13 hours ago [-]
> but also the prohibition on using the unsafe API in the entire program and all dependencies. Which Rust could also do in theory but not yet in practice AFAIK.
It is possible, check MaulingMonkeys' comment upthread.
wildfireday2 23 hours ago [-]
Complete memory safety (much less concurrent garbage collection) isn’t really workable for things like kernel and some embedded programming contexts. Rust can work in those contexts precisely because it has an unsafe out.
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.
pizlonator 18 hours ago [-]
mmap is a great example of Fil-C providing a unique level of safety.
You can’t use mmap in a way that corrupts memory in Fil-C
Try it. :-)
quotemstr 1 days ago [-]
Okay, let me know when I can call process_vm_writev or ptrace and have Fil-C verify safety properties on the result.
jcranmer 1 days ago [-]
Don't forget reads and writes of /proc/self/mem! :-)
modeless 24 hours ago [-]
Better include Rowhammer too. Maybe Fil-C should run a test and refuse to start on any system with bad RAM or unpatched CPU errata. It could also monitor the voltage to protect against undervolting attacks. And you'll need some cosmic ray shielding too.
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.
pizlonator 24 hours ago [-]
Sure all of those are escape hatches that work against any memory safety tech.
Point is, Fil-C goes further than any other memory safety tech in terms of what it guards
modeless 23 hours ago [-]
Fully agreed. At some point you have to draw a line and say that the rest is the responsibility of the kernel and hardware and user, and I think Fil-C drew that line in the right place.
josephg 22 hours ago [-]
How does it compare to the equivalent code in Typescript, Go or C#? Those languages all have “safe” syscall wrappers too, for a subset of syscalls.
pizlonator 22 hours ago [-]
Those languages rely on a much larger pile of YOLO C/C++ code for their runtimes and standard libraries than Fil-C does.
So Fil-C is safer than those
josephg 21 hours ago [-]
The number of lines of raw C isn’t the only way to measure the trustworthiness of a codebase. Go, C# and friends may be bigger projects. But they’re also more mature projects. At this point, far more eyeballs have scoured their codebases looking for security bugs.
> 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.
Splizard 21 hours ago [-]
Small nitpick but Go doesn't really have any YOLO C/C++ code in its runtime and standard library.
pizlonator 20 hours ago [-]
Yeah what I said is true of TS and C#, but not of Go.
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.
quotemstr 20 hours ago [-]
Races in Fil-C allow access to one object through a pointer to a different object if there's an attacker-controlled offset involved.
Fil-C's safety guarantees therefore fail to apply in this situation.
pizlonator 18 hours ago [-]
Nonsense.
In a race, you at worst access an object you could have loaded from whatever field you were racing on.
In Go, you can bypass all protections and corrupt all of memory if you race on structs (I think).
quotemstr 17 hours ago [-]
> In a race, you at worst access an object you could have loaded from whatever field you were racing on.
Suppose a pointer ptr is initialized to foo and transitions once to bar; suppose also that x is attacker-controlled. Then,
T1: T* p = ptr; if (p == bar) p[x] = 7
T2: ptr = bar
A program can observe p == bar (testing the address bits) but still permit a write to foo through p (allowed by the stale capability bits after offset by attacker-controlled x), allowing T1 to perform an unexpected mutation of foo.
No, you do not always trap in this scenario, as you've claimed repeatedly on X. You perform the capability check after combining p and x. If an attacker sets x == foo - bar, then p[x] refers to memory inside foo even if p == bar.
Because (for understandable reasons) you don't insert a memory barrier between a write of a pointer's address bits and its capabilities, use two-word atomic accesses, STM, or in any other way synchronize writes to pointer addresses and capabilities, programs with data races can observe arbitrary combinations of linear addresses and capabilities that go along with them.
Plenty of exploit chains have had humbler beginnings.
This access-foo-through-pointer-to-bar scenario can't happen in Java. It can't happen in CHERI. It can happen in Fil-C. Yes, T1 at one time had a capability on foo, but the programmer intent is clearly to mutate only bar, and Fil-C allows an execution that mutates foo instead.
Elsewhere, you've claimed such executions cannot be exploited. I am skeptical of this claim given previous exploits that began by the camel poking its nose through similarly innocuous-seeming holes in the tent.
Fil-C cannot fully protect C programs from exploitable memory corruption caused by violations of the C virtual machine. 99.9% of practical ones? Sure! Fil-C is good stuff. But there are holes (not only here, but for arenas, intra-object corruption, etc.), and these are holes that safe Rust prevents. Fil-C and Rust rules prohibit different (but mostly overlapping) classes of exploit.
Could you define Fil-C's behavior as "memory safety"? Sure. You can define words to mean anything. You cannot, however, define Fil-C as something that just deletes the security implications of bugs in existing C programs. Does it mostly achieve this goal? Sure. Does it supply comprehensive coverage? No! It's a hardening tool, not a panacea, and it would behoove you to represent it as the useful tool it is, not magic pixie dust that makes C safe.
Please stop ruining a tool as good as Fil-C by claiming it does things it does not and cannot.
> In Go, you can bypass all protections and corrupt all of memory if you race on structs (I think).
This is my understanding as well. I'm glad we agree, opinions on Fil-C memory model counting as "safe" aside, that Go is awful.
pizlonator 16 hours ago [-]
The “bug” in your example hinges on this:
> if (p == bar)
It is not in scope of memory safety to make sure that logic not related to memory accesses works as you expected.
In Fil-C, the integer pointer value (the intval) is not trusted. You could get it wrong with things more sexy than races (integer overflows or just plain bad math). Fil-C just guarantees that your accesses obey the capability model, which is true in your example - the only object the program can access is whatever object the capability you loaded points to.
You’re being disingenuously imprecise when you use this framing:
> access-foo-through-pointer-to-bar
In fact, you can only access foo if bar’s capability referred to foo, and that can only happen if the thing being raced on (the pointer in shared memory) had a prior store to it that had foo’s capability.
Hence, this isn’t an arbitrary memory access. This is a memory access that obeys the capability model. It’s a memory access that would have been possible even if the program had no race.
> Please stop ruining a tool as good as Fil-C by claiming it does things it does not and cannot.
You are mischaracterizing the issue to make it seem like it’s a memory safety issue, when it’s not. I think that is doing more damage than anything I have said
quotemstr 16 hours ago [-]
> It is not in scope of memory safety to make sure that logic not related to memory accesses works as you expected
This logic is related to memory accesses. This is not an issue that can occur in systems that provide the memory-safety guarantees that the words "memory safe" usually name.
You can't escape failure to meet a public definition of a term by adopting a private definition. The English language has a term for this pattern.
> You’re being disingenuously imprecise
You are the one being imprecise. You claim that because T1 could access foo at some time in the past, it's okay for a subsequent execution to access foo when the program text limits the access to bar. The temporal history is irrelevant. You are taking language that applies to inter-actor access control and applying it to invalid execution detection.
If T1 and T2 were, say, mutually untrusting actors over a network, that T1 had a capability at one point would be a valid defense. We're talking about a totally different scenario, not an object-capability security system, but preventing attackers turning illegal C into various kinds of exploit, e.g. EOP, information disclosure, and so on.
It does not matter that T1 used to have the capability and could have used it. We're talking about an attacker using an exploit to make T1 do his bidding, not T1 itself being a hostile actor running in a sandbox.
You can't just define the problem away. Real-world C has these races. They've been exploitable. Your system doesn't close them.
> Hence, this isn’t an arbitrary memory access.
Not arbitrary, true. Irrelevant. It's a cross-object access that violates programmer expectations and is likely exploitable.
> This is a memory access that obeys the capability model
You're using the English words that denote a strong guarantee in the security community and using them to describe your weaker system.
> I think that is doing more damage than anything I have said
Damage to what? You can't fix this problem in Fil-C without introducing atomics to every globally-visible store path, so you're going around the internet trying to play definition games to define it out of existence. The only damage here is to people who think Fil-C provides stronger mitigations than it does.
ncruces 21 hours ago [-]
Go doesn't rely on much C/C++, not recently at least. Particularly on Linux.
quotemstr 21 hours ago [-]
If we strip away the semantic maneuvering, your argument becomes this: Rust is unsafe because it lets people who aren't you, Pizlo, write unsafe code. Safety means trusting only you with machine code.
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.
minraws 1 days ago [-]
It doesn't work for a significant set of syscalls and is very very unlikely to ever work because of how the syscalls themselves work unless ofc you are willing to emulate them somehow which is a insane amount of additional compute cost.
pizlonator 24 hours ago [-]
Custom libc does not call to system libc.
“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)
wasmperson 23 hours ago [-]
The way I see it, as far as memory safety is concerned that's just framing. Both Fil-C and Rust have a boundary below which the unsafe lives. If Fil-C is only safer than rust when you don't pass the `-F unsafe_code` argument to rustc then IMO "safer" in this case is somewhat of an empty claim.
flumpcakes 23 hours ago [-]
I think you should watch the linked presentation, it will show how it is "safer" than rust. Unfortunately there are performance implications, but fil-c seems fast enough to go into production for many workloads and surprisingly needs very little code changes to existing C/C++ projects which is a huge benefit.
josephg 22 hours ago [-]
I think Fil-C might make a lot of sense for running legacy C or C++ codebases. But for new code, it seems like it’s trying to compete with other GC languages. Take away C’s performance advantages and I don’t know why anyone would use it.
Fil-C: Combining the ergonomics of C with the performance of Python!
uecker 15 hours ago [-]
I like using C. It is simple, elegant, has very fast compilation times, requires no FFI needed for many libraries, is extremely stable and extremely portable, a large ecosystem, and, most importantly, I do not think the ergonomics of C are bad.
It is inconvenient to start using it as it comes with basically nothing out of the box, but this is irrelevant after a short time using it.
If I'm understanding correctly, it's this general syscall function that's safe in Fil-C.
cypherpunk666 1 days ago [-]
indeed, the ideas could be used more widely.
fil-c runs on linux.
what if linux ran on fil-c?
josephg 22 hours ago [-]
Do you want your kernel to be 2x slower and use 4x as much memory?
wavemode 21 hours ago [-]
A computer spends the vast majority of its time and memory in userspace, so this tradeoff isn't as bad as it sounds.
josephg 21 hours ago [-]
The benefit also isn’t as big as you might expect. Most of Linux’s recently found security vulnerabilities were due to ToC/ToU bugs. Fil-C would not magically fix these problems.
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.
cypherpunk666 13 hours ago [-]
a fun exercise is to ask an LLM how it might be applied.
avadodin 13 hours ago [-]
You can run your Fil–C userland on provably–secure seL4.
(Left as an exercise for the reader)
josephg 11 hours ago [-]
If you’re using SeL4, userland processes are already strictly sandboxed. There’s still some benefit to Fil-C, since the added memory safety would make it much more difficult to take over a process. But the blast radius of a compromised program in SeL4 is much smaller because of the capability model.
yjftsjthsd-h 1 days ago [-]
I was under the impression that fil-c's memory management (gc?) wouldn't work on a kernel? Be sweet if you could, ofc
brabel 12 hours ago [-]
This is a myth that seems to never die. OS kernels can and have been written in GC languages. Watch the video maybe? The whole presentation was running on a distro fully compiled with FillC.
doctorpangloss 1 days ago [-]
what if fil-c could draw pelicans?
cypherpunk666 1 days ago [-]
also: why not use fil-c to improve cython, and the ffi.
FuckButtons 1 days ago [-]
the only reason to use cython (in my experience) is to write quick numpy extensions without a tonne of extra baggage from having a complete compiled extension in C/C++/Rust. Often, you explicitly want to avoid the compiler offering you any kinds of additional checking because you want to get maximum throughput. Most of the functions I’ve written in cython explicitly opt out of bounds checking etc since the memory access is sequential and bounded by construction and very obvious when you mess things up by writing simple unit tests. Given that, it doesn’t seem useful to me to add any kind of memory safety as additional overhead. YMMV I mostly do scientific computing and signal processing.
josephg 22 hours ago [-]
If you want a slow, garbage collected language in the Python ecosystem, why not just write Python?
cypherpunk666 13 hours ago [-]
you would. python and ffi would have runtime sanity checks a la fil-c. better error messages too. ask your favorite LLM how it would look.
LoganDark 1 days 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.
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"
raggi 1 days ago [-]
This presentation wasn’t too bad in terms of us vs theming, but in general throughout the lifetime of the project there’s been a strong us vs them rhetoric. I think it’s working as a marketing tactic to some extent but there are better, albeit harder ways to market the thing.
LoganDark 1 days ago [-]
Rust generally invites us vs them everywhere because it's just so ambitious and attractive (to some). Fil-C probably receives questions all the time about why they don't just do X or Y that Rust does. Maybe they end up supporting their position against that type of pressure and it looks like they're opposing Rust when all they're doing is justifying themselves.
pjmlp 8 hours ago [-]
Because by making Cyclone's ideas mainstream, which AT&T started alongside Cornell University, the C and C++ devs that so far felt safe from all those RC/GC languages, now had an actually problem as companies started paying attention and embracing the language's ideas, even extending the type system of existing RC/GC languages.
Thus anyone that identifies themselves with the programming language they work with, gets dragged into a us vs them discussion.
uecker 1 days ago [-]
Arguably the most important memory safety property, i.e. bounds checking for dynamic arrays, is also not statically checked in Rust.
SkiFire13 4 hours ago [-]
But that's also arguably pretty boring because it's very easy to dynamically check and the cost for doing so is relatively small compared to what needs to be done for other properties.
On the other hand statically checking bounds for (dynamic and non) arrays is very very hard in the general case.
woodruffw 1 days ago [-]
How would Rust perform static checks on dynamic bounds? That seems like an impossible order, i.e. not a reasonable evaluation criteria for any (general-purpose) language.
(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.)
zem 1 days ago [-]
ATS could probably do some of this by constructing proofs about the bounds and indices - not in the completely general case of course, but for at least some fraction of what a language without dependent types would have to defer to runtime
ratmice 1 days ago [-]
those are not dynamic bounds.
LoganDark 1 days ago [-]
All "dynamic" means is that you don't know or don't prove the precise value statically. However you may know the range of possible values, or you may know properties of your algorithm that mean it can never attempt an out-of-bounds access. Sometimes you don't know any of these things, but sometimes you do.
ratmice 23 hours ago [-]
What I was (badly) trying to express was more that given static bounds rust could also eliminate dynamic checks. So saying e.g. ATS can eliminate static checks, is kind of switching the target.
Presumably, they're talking about the possibility of explicitly replacing dynamic bounds checks in cases where it's statically provable that the access will not overrun. That doesn't necessarily mean you lose cases where you genuinely can't prove such a thing, but rather that it would be possible to opt into compiler assistance with proving it, rather than hoping LLVM will have your back. At least, that's how I'd envision such a thing for Rust. This would be similar to how you can already choose whether to invite more of the borrow checker by using references directly, or to do the checks at runtime with Rc/RefCell, etc.
woodruffw 1 days ago [-]
Yeah, that seems like a nice thing that Rust could offer. It strikes me as a weird thing to get hung up on, though, given that the norm in compiled languages - including C - is to express your bounds such that an optimizing compiler can (but won't necessarily) eliminate them.
(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.)
haberman 1 days ago [-]
> rather than hoping LLVM will have your back
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.
I merely pointed out that the statement "Rust prefers to prevent all undefined behavior statically" is misleading in the sense that Rust does not do this for all undefined behavior.
LoganDark 24 hours ago [-]
Maybe if you think of [] as offsetting a pointer rather than calling into an Index (or IndexMut) implementation. Since the return type isn't optional, a panic is the only way to avoid performing an invalid access or manufacturing an unfaithful return value. There are also optional accessors which do not panic, and unsafe/unchecked accessors.
uecker 23 hours ago [-]
Maybe? I think my statement is clearly correct in the way I formulated it.
uecker 1 days ago [-]
Rust can not do this. But the original claim is that it is all statically checked in Rust, and this is an obvious counter example. 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.
woodruffw 24 hours ago [-]
> But the original claim is that it is all statically checked in Rust
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.
uecker 24 hours ago [-]
I read "Rust prefers to prevent all undefined behavior statically," to also imply that it actually does this, because otherwise the word "all" would not make sense to me in this sentence.
Dependently types languages and model checking do exist. They come with tradeoffs, but this is also true for Rust.
wasmperson 1 days ago [-]
This isn't entirely true. Rust's indexing operator performs a dynamic check but the language and stdlib have a number of ways to access array elements without indexing, which is effectively a form of static checking. For example, the following loop is statically guaranteed to not go OOB, and will not emit any unnecessary bounds checks:
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.
uecker 1 days ago [-]
These are not terribly interesting cases though, as it would also be impossible to get an oob access in other languages either.
wasmperson 23 hours ago [-]
Yes, rust didn't invent the concept of an iterator, and is thus not the only language which offers a solution to statically avoid bounds checks. Feel free to give a more "interesting" case you believe rust wouldn't be able to handle.
uecker 23 hours ago [-]
Rust fails to prove basic indirection to be statically safe and instead does a run-time check.
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.
uecker 22 hours ago [-]
A: "Rust prefers to prevent all undefined behavior statically"
me: It does dynamic bounds checking.
Rustaceans: But... for XYZ ... it doesn't...
Sigh.
steveklabnik 22 hours ago [-]
It's because you're not using words in the way that people use them when talking about this stuff.
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.
uecker 14 hours ago [-]
Rust does not do prevent all UB statically. Even if you guarantee at compile-time that all execution paths have well-defined semantics because compiler inserted run-time checks, this is not the same thing as "statically ensuring the absence of this run-time condition that would cause UB" which would be what people usually mean with "statically preventing UB". So I think it is not me who uses the works in an unusual way.
If, of course, you redefined words so that adding run-time checks counts as "statically preventing all UB", then this applies to Fil-C just as well! You can not have it both ways.
josephg 22 hours ago [-]
These are all true? I don’t see the problem?
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.
uecker 14 hours ago [-]
If we can all agree that my statement was true, there is no problem.
wasmperson 22 hours ago [-]
Now it's your example that's too simple. I can't present a counter-example that wouldn't look totally different: if you avoid the indexing operator then trivially incorrect code like that becomes unrepresentable. It's like if I said destructors help you avoid memory leaks and you asked how you'd avoid a leak in a single-function program that does nothing but call `Box::leak()`.
Again: I'm not disputing the fact that the rust index operator does a dynamic bounds check.
uecker 14 hours ago [-]
If you agree with that Rust sometimes has to add dynamic bounds check, then you could have just agreed with my original comment.
wasmperson 5 hours ago [-]
[dead]
LoganDark 1 days ago [-]
It's possible to do this (WUFFS does it) but it's so invasive that, well, you basically end up with WUFFS. You end up having to track every set of possible and impossible values at every location, etc. (during each program location too.) WUFFS certainly has its place and I've kind of been itching to try it out myself, but I think getting to the point where dynamic arrays can be statically bounds-checked would have highly disabled Rust.
n42 1 days ago [-]
The combination of static safety gracefully degrading to runtime safety where not provable is interesting to me
LoganDark 1 days ago [-]
This is sort of already possible in Rust when you use safe primitives (e.g., RefCell), but it doesn't cover unsafe code invoking Undefined Behavior. Placing something like Fil-C around unsafe code could prove to be interesting, especially since current approaches rely on interpretation (Miri, Soteria Rust) rather than translation.
quotemstr 1 days ago [-]
Both Rust and Fil-C have unsafe blocks, but in Fil-C latter system, only Pizlo gets to write them. Why am I not filled with confidence?
pizlonator 24 hours ago [-]
Fil-C has no unsafe blocks
rurban 10 hours ago [-]
But it still defers compile-time errors to run-time. Not safe enough for me. I rarely have 100% coverage
bonzini 14 hours ago [-]
Is the runtime compiled by the Fil-C compiler, or is it a single huge unsafe block? I honestly don't know the answer.
JoelJacobson 14 hours ago [-]
I was amazed by the presentation all the way up until 33:47, where the buggy program compiles and only errors out when the invalid access is executed. So apparently Fil-C enforces memory safety dynamically at run-time, rather than detecting these bugs at compile-time.
A new language designed around memory safety, such as Rust, can reject large and important classes of memory-safety bugs at compile-time. Rust does not catch everything at compile-time, like bounds checks and RefCell, which are checked at run-time(, and the problems due to unsafe and C interop like explained in the presentation.)
Still, IMO the comparison becomes a bit apples vs pears when bragging about how much more memory-safe Fil-C is than Rust. It would have been helpful to explain this important difference about run-time vs compile-time.
Very cool and useful anyway.
flumpcakes 13 hours ago [-]
I thought it was very obvious throughout the video what was happening...
Rust has compile time checks to enforce some safety. In comparison Fil-C is a lot more memory safe, it's more comparable to being a software implementation of CHERI.
Unfortunately it comes with downsides: runtime performance, runtime enforcement, granular safety (the safety is around allocations).
It also comes with huge upsides: you can run C/C++ with little to no code changes. Imagine compiling nginx and the associated system libraries with Fil-C, the performance hit is probably acceptable and now the web server is memory safe.
Rust probably provides enough memory safety (even if it is not complete safety) in most circumstances though.
JoelJacobson 12 hours ago [-]
I'm not a native English speaker, I know "memory safe" has a precise technical meaning, but the word "safe" still feels a bit strange to me given that a memory-safety bug can make the program crash at run-time.
Sure, Fil-C prevents the bug from possibly being exploited, which is a huge improvement. But crashing can be a DoS attack, and if running a mission-critical system, it might not be an acceptable outcome.
I just feel the already very good presentation could have been made much better if it had put more weight on explaining these trade-offs.
philippta 2 hours ago [-]
Even in memory safe Rust, out-of-bounds array access also results in crashes.
The whole point of memory safety is that bugs can not be exploited. A memory safe program does not mean a crash safe program.
reanimus 14 hours ago [-]
The author's whole schtick is purposefully rage-baiting Rust devs, so really not that shocking that he downplays the compile-time trade-off
tialaramex 23 hours ago [-]
Ha, looking through the links dang added, I came upon something I wrote back in December
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.
bonzini 14 hours ago [-]
How does it do that? When the 64-bit pointer is actually a pointer, does it mangle it into something that is certainly not UTF-8 (e.g. changing its top byte to 0xC0, and assuming that bits 56-63 are the same as bits 48-55, or something like that)?
tialaramex 13 hours ago [-]
UTF-8 is a self-synchronizing encoding, so each UTF-8 byte tells you whether there were any previous bytes you'd need in order to understand it. Since we're storing a whole string our first byte logically cannot be such a "continuation byte", if it seems to be, we can say these eight bytes aren't UTF-8 encoded text but instead a pointer. The UTF-8 "continuation" marker is that the top bit is set and the next bit is not. So, we need to smash two bits of our pointer.
Now, by pointing only at memory aligned to 4 bytes, the way a 32-bit integer would be on a typical machine, the bottom two bits of the pointer are always zero. So, we ask for such 4-byte aligned pointers when allocating memory to point at, most allocators actually never give out smaller alignments anyway because they cause problems in C but we've told the allocator our requirement so even if it could it won't give out unaligned pointers.
Finally we need to re-arrange our pointer so that those two zero bits are in the right place to put a UTF-8 continuation marker. In Rust this is legal safe code, the map_addr method on a pointer is allowed to twiddle with a pointer's address bits (on typical platforms, all of them) and so long as your transform is reversible (which this one is) it will work correctly when you twiddle the pointer back and dereference that pointer in unsafe Rust.
bonzini 11 hours ago [-]
Ah, so it is basically a single rotr(x|2, 2), nice
ndesaulniers 15 hours ago [-]
I like the inline asm sanitizer; probably could be extracted out of Fil-C.
Panzerschrek 5 hours ago [-]
Who is the author of this project? Is it a single person? A small team? A single person with LLMs?
ivanjermakov 35 minutes ago [-]
Filip mentioned in the talk that Fil-C is his personal project.
zdimension 4 hours ago [-]
Filip Pizło (https://www.filpizlo.com/), according to his website: Director of Programmability and Data Model at Roblox. Previously, I worked at Epic Games (Senior Director of Language Engineering) and Apple (Manager of the JavaScriptCore Team).
Question for the author from someone with no expertise in the field: how does Fil-C different from https://security.apple.com/blog/memory-integrity-enforcement...? I'm guessing the hardware of the latter is something that Fil-C doesn't necessarily rely on.
LugosFergus 1 days ago [-]
I was just thinking that we were overdue for a Fil-C post on HN.
quotemstr 1 days ago [-]
Every one of these posts is the same. There's this weird blindness to the problems and imperfections in Fil-C --- just shouting down. It reduces my confidence in Fil-C as a whole.
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?
dundarious 1 days ago [-]
> claim that other systems are worse in ways they are not (e.g. with respect to Rust having unsafe blocks)
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.
NobodyNada 23 hours ago [-]
Machine code is unsafe, so any memory-safe language must necessarily be built on some unsafe code somewhere. Safety is always conditional on the underlying unsafe implementation having no bugs.
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.
pjmlp 14 hours ago [-]
Machine code could be made safe actually, however most research topics regarding strong typed Assembly, or verified Assembly, failed to gain mindshare among commercial vendors.
Reasonable people can disagree with you on the point that Rust is more safe because you can write unsafe code in Rust, whereas in Fil-C you write unsafe code in the compiler.
Why might I write unsafe Rust? To do something not particularly fancy. A use case covered by normal safe Fil-C. Why might I write unsafe Fil-C (compiler) code? To improve compiler or runtime performance or add new platforms, etc. Analogous to wanting to work on Rust's IR or borrow checker.
Very different use-cases.
flumpcakes 23 hours ago [-]
There are no 'unsafe' blocks in Fil-C. I think you trying to conflate 'unsafe blocks' with the fact that there might be compiler errors which might break 'safety'? Which is equally true in any system including Rust.
NobodyNada 23 hours ago [-]
Exactly, my point is that the compiler and runtime are the "unsafe" portions of Fil-C, and that Fil-C and (safe) Rust are equivalent in this regard.
quotemstr 21 hours ago [-]
Any safe system has unsafe parts. Quibbling about whether these parts are called "blocks" isn't useful. The term changes nothing.
dundarious 7 hours ago [-]
Pobody's Nerfect is not the mindset that led to Rust's innovations, nor Fil-C's.
"Blocks" are relevant as they are how you express programs, algorithms, etc., that you need in order to get something done. Lots of data structures in Rust have a little unsafe somewhere. Users will typically depend on some "specialist" crate author to write them, but it exists, and is a necessary part of practical Rust programming. The pool of unsafety is open and by necessity growing.
In Fil-C, no such specialists are needed, and the pool of unsafety is closed and fixed, no matter what programs, algorithms, data structures you use.
This is not a trivial distinction I think. I think it's easy to acknowledge, especially given its (current) performance cost.
quotemstr 6 hours ago [-]
That's a lot of words to say that the problem with Rust is that people you don't like are allowed to write unsafe code.
dundarious 6 hours ago [-]
My god, you are insufferable, that's not my opinion at all. Just because I used quotation marks in one place, a practice that can convey multiple meanings, not all of which involve disdain. It's unfortunate that Rust, a good language that has more practical use than Fil-C, a language that I use more than Fil-C, has proselytizers such as you.
quotemstr 2 hours ago [-]
Your argument is literally that there is too much unsafe code.
23 hours ago [-]
mshroyer 23 hours ago [-]
He's been especially arrogant and dismissive on X. I'm very glad Fil-C exists, but his behavior makes me doubt the whole project.
mshroyer 4 hours ago [-]
To be concrete and hopefully more constructive with my criticism, he's prone to saying things like "Fil-C is safer than Rust":
These are false claims. The following compiles and runs fine with Fil-C, happily corrupting memory in a way that a safer language like Rust would not allow:
His claims about Fil-C being safer than Rust seem to hinge on Fil-C's ability to make safe-er an entire application stack, compared to a hypothetical Rust application that links to C dependencies which (currently) can't be compiled with Fil-C. This is true as far as it goes, but it's a far cry from justifying a blanket statement like "Fil-C is safer than Rust".
metaltyphoon 16 hours ago [-]
Mark? AwareDigital 2013?
mshroyer 4 hours ago [-]
Hey drop me an email
pizlonator 24 hours ago [-]
If you think you like Fil-C but aren’t able to see the specific way in which it’s better than Rust (more comprehensive safety), then what is it that you’re liking?
NobodyNada 22 hours ago [-]
Not the parent, but: I strongly dislike this framing of "Fil-C is better than Rust" or "Rust is better than Fil-C". This is apples-to-oranges; users will almost never be comparison-shopping between the two because they address almost entirely different problems.
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.
pizlonator 22 hours ago [-]
I’ve never said that Fil-C is better than Rust full stop.
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
wild_pointer 19 hours ago [-]
Let's not shy away then. What details do you disagree on?
P.S. Also fan of your work. Competition is always good.
pizlonator 17 hours ago [-]
Rereading your post, I think I just disagree on two things:
I don’t think Rust users can be trivialized into the “two kinds” that you list, and if one of the kinds is “app developer” then I bet you there are apps where Fil-C’s value proposition is exactly right, except just the fact that Fil-C is so new and immature. Say you want to ship a native UI. Using GTK from fil-C is fantastic.
And I specifically disagree with:
> 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.
When I write new code in Fil-C, I just lean into the GC all the way, which makes programming in C and C++ so much nicer. I don’t ref count, I don’t use smart pointers, I don’t free and I don’t delete. It makes these languages so much nicer!
Also, Fil-C’s guarantee that it will panic on OOB or if a race goes badly means I spend basically zero time debugging memory safety issues. The reliability of the failures totally changes the dev experience for the better.
That’s subjective obviously. Some folks swear by type systems like Rust’s to catch as many issues as possible. That’s just not how I roll.
All of that said - the reason to use rust and not Fil-C is performance and memory usage. Fil-C isn’t there yet, except for maybe a small handful of cases (like BLAKE3 and xzutils, where the overhead is basically zero for some reason … we’ll probably because I did a lot of compiler opts and sometimes you get lucky and they sort of all hit)
wild_pointer 7 hours ago [-]
> That’s subjective obviously. Some folks swear by type systems like Rust’s to catch as many issues as possible. That’s just not how I roll.
Yeah, I like it. I also like not caring about dangling pointers, UAF, and other things, not even because of security. These are just bugs that are not fun to debug.
But I get the appeal of the freedom of C++. Especially if you deal with FFI and low level stuff, Rust either forces you to write safe wrappers (good investment long-term, but not fun to do), or just use lots of unsafe, in which case there's no benefit.
This, and there's also cargo which is convenient.
> When I write new code in Fil-C, I just lean into the GC all the way, which makes programming in C and C++ so much nicer.
Then it's no longer C/C++. More like C++/CLI maybe. And it probably gets tricky with FFI. Anyway, keep it up on making the world a safer place :)
nu11ptr 10 hours ago [-]
> When I write new code in Fil-C, I just lean into the GC all the way, which makes programming in C and C++ so much nicer. I don’t ref count, I don’t use smart pointers, I don’t free and I don’t delete. It makes these languages so much nicer!
Sure, GC's are nice which is why so many langs have them, but that removes the deterministic allocation performance which most C/C++ programmers want (and many times need). Why not just use something like Go then? You have a much richer stdlib available out of the box.
quotemstr 17 hours ago [-]
> Also, Fil-C’s guarantee that it will panic on OOB or if a race goes badly
Fil-C as currently implemented does not guarantee panics on unsafe accesses due to races. You dodge the problem by using a private definition of safety under data race that permits program executions nobody would expect.
pizlonator 17 hours ago [-]
I define memory safety in terms of capabilities, which is a mainstream definition.
The worst that can happen in a race is that you read or write an object that would have been accessible even in the absence of races.
The thing that makes races hard to debug in C or C++ is memory corruption; that doesn’t happen in Fil-C
mirashii 12 hours ago [-]
I’m having a hard time understanding how these two things can be true at once. Just because the capability exists that somewhere in my program a valid pointer to a piece of memory might exist (what I understand accessible to mean), doesn’t mean that a particular write to that memory location under a data race that tears a pointer is valid. The data race may make a pointer that would never algorithmically appear in the program in the absence of races, and thus make writes that invalidate invariants that should have been preserved. It seems to me this still provides a way to corrupt memory in a difficult to debug way just like C. Am I missing something?
surajrmal 18 hours ago [-]
It provides a way to improve the safety of existing code without rewriting it. It would be nice if it could solve the memory safety issues around data races too.
jagadaga 1 days ago [-]
>I've long faulted Rust's use of Result over exceptions
Why?
AlotOfReading 23 hours ago [-]
Not the person you're asking, but result types pessimize code more, add register pressure, use more icache for largely dead code, etc. They're arguably noisier at the source level too.
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.
jagadaga 14 hours ago [-]
> result types pessimize code more
What does it mean?
> They're arguably noisier at the source level too.
But they are more explicit, which means there will be fewer bugs in your code.
pjmlp 14 hours ago [-]
C++ noexcept exists because the way C++ evolved, it is no argument against exceptions.
Exceptions came to be during the 1990's, as the C++ARM to C++98 standardisation process was taking place.
Naturally during the early days C++ compilers lacked exceptions, as CFront was introduced without them.
Then we had the C folks that were migrating to C++.
When C++ compilers finally supported exception, compilers vendors introduced the non standard switch to disable them, so that existing code could still compile with the expected behaviour.
The standard itself does not acknowledge this as allowed.
This feature was naturally misused by the anti-exception folks, same applies to how RTTI came to be.
So noexcept is a way to be able to write code to appease both camps, while surfacing what variant was chosen by the programmer.
From my point of view allowing disabling exceptions in first place was a mistake, those folks should have moved back to C, or fixed their code.
MrBuddyCasino 13 hours ago [-]
> That said, I've spent too much of my life chasing implicit control flow to accept exceptions.
Yes. This is why unchecked exceptions are annoying. Checked exceptions are a „failed experiment“ anyway.
A result type (or even Go‘s err) with forced error handling meaningfully increases the robustness of a program in my experience.
nu11ptr 4 hours ago [-]
Checked exceptions were actually the "right way" to do exceptions (if there is such a thing), the problem was just that developers hated them, but I think that draws the wrong conclusion. That tells me their syntax/usage was seen as too much forced boiler plate making code unwieldy, not that they didn't have benefits for correctness (something often not appreciated until years later).
Unchecked exceptions lead to unhandled exceptions at runtime. We've all seen screens with Java programs running with tons of exceptions in the log, or worse, that crash with unhandled exceptions. This is the result of the unchecked exceptions mess, which is why I won't use languages that use them for routine error handling for anything more than trivial programs.
MrBuddyCasino 26 minutes ago [-]
> developers hated them, but I think that draws the wrong conclusion
Exception handling is just annoying from a syntax perspective. Also checked Exceptions have the problem that they bubble up types that a different layer shouldn’t even be aware of due to exception chaining (cause of a cause etc), unless you carefully re-throw them, which nobody did.
Lower ceremony errors are just better to deal with.
_flux 8 hours ago [-]
My view is that Java's checked exceptions failed in big part because Java didn't have type variables back then; after all, polymorphic Result types are not that much different from checked exceptions.
I haven't written any Java since it has gained those, so I suspect there might be other aspects making it awkward. And it still would lack exhaustiveness checking that e.g. Rust has.
anon-3988 15 hours ago [-]
I don't understand why Result type is necessarily slower. Result that simply bubbles up is basically an exception, no?
AlotOfReading 15 hours ago [-]
You need instructions to check the result type, and a bit somewhere to store whether it's valid. Most exceptions only add extra instructions during throws/unwinding. There may or may not be an overall reduction in code size, depending on how many checks you have vs table entries.
jagadaga 14 hours ago [-]
In theory, a compiler can figure out that you don't need that in some cases.
wasmperson 23 hours ago [-]
The worst part about rust not having exceptions is that it actually does have exceptions, you just shouldn't use them. All the downsides and none of the benefits:
Just like on Go's case, panic/recover are exceptions with bad ergonomics.
kibwen 10 hours ago [-]
This is a vacuous statement on par with characterizing setjmp/longjmp in C as "exceptions with bad ergonomics".
pjmlp 9 hours ago [-]
Which they kind of are, and were used as implementation mechanism for C++ exceptions in the early days on UNIX compilers.
kibwen 10 hours ago [-]
No, Rust does not have exceptions. Rust has unwinding, if you choose to compile your program with support for it, which you are free not to, and is trivially achieved by a single flag.
wasmperson 6 hours ago [-]
It's hard for me to think of a definition of "exception" which doesn't include Rust's panics:
use std::panic::catch_unwind;
fn throws_exception(){
panic!("hello");
}
fn main(){
match catch_unwind(|| {
throws_exception();
println!("Never reached");
}) {
Ok(_) => (),
Err(e) => println!(
"threw an exception: {:?}",
e.downcast_ref::<&'static str>().unwrap())
}
}
I can disable exceptions in rustc, but I can't disable the influence they have on language and library design (as detailed in the link I posted). Exceptions are the main reason you can't temporarily move something out from behind an exclusive reference, or return an error code from a Drop impl. Heck, Rust wouldn't even need destructors if it weren't for exceptions: the compiler could just tell you when you forgot to free something.
uecker 23 hours ago [-]
He claims the system provides memory safety under data races. Does it not?
Yet another prove of the idea that worse is better.
First people use unsafe poorly-designed languages for writing critical software (already worse). Then someone invents a hack making code 4x slower and consuming 2x more memory to "fix" safety issues (much worse).
Rendered at 21:06:37 GMT+0000 (Coordinated Universal Time) with Vercel.
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
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.
Adding Fil-C-like runtime checks to Rust is definitely an interesting direction.
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.
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.
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.)
[1]: https://docs.rs/nix/latest/nix/index.html
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.
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.
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.
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.
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)
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.
JavaScript at #6, when it should be at #1? Trailed by Visual Basic at #7? SQL (not a programming language) at #8 and R (have you ever seen an R job?) at #9. Then you have Rust just barely squeezing past Delphi? Followed by scratch???
Come on, this is not a serious source. Neither are the the rest really. Any popularity/usage list that doesn't have JS at first place is not serious.
Yeah I know the feeling. But you don’t need Fil-C to learn C.
That said, with a GC, you could make some great tools & visualisations for learning C and understanding the memory layout of a running C program. That would be cool to see
If python/go/C# is easier to write, why choose C? Fil-C removes C’s performance and interoperability advantages.
TBH C# is probably a good default for a lot of projects, from a safety and performance standpoint (rust's big performance win over GC/JIT languages is more in the memory consumption than throughput, counterintuitively). Fil-C probably still has a pretty good edge on python's performance, though.
Or you could write a library that works for Fil-C and C without writing the library twice.
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.
It is possible, check MaulingMonkeys' comment upthread.
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.
You can’t use mmap in a way that corrupts memory in Fil-C
Try it. :-)
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.
Point is, Fil-C goes further than any other memory safety tech in terms of what it guards
So Fil-C is safer than those
> 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.
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.
Fil-C's safety guarantees therefore fail to apply in this situation.
In a race, you at worst access an object you could have loaded from whatever field you were racing on.
In Go, you can bypass all protections and corrupt all of memory if you race on structs (I think).
Suppose a pointer ptr is initialized to foo and transitions once to bar; suppose also that x is attacker-controlled. Then,
A program can observe p == bar (testing the address bits) but still permit a write to foo through p (allowed by the stale capability bits after offset by attacker-controlled x), allowing T1 to perform an unexpected mutation of foo.No, you do not always trap in this scenario, as you've claimed repeatedly on X. You perform the capability check after combining p and x. If an attacker sets x == foo - bar, then p[x] refers to memory inside foo even if p == bar.
Because (for understandable reasons) you don't insert a memory barrier between a write of a pointer's address bits and its capabilities, use two-word atomic accesses, STM, or in any other way synchronize writes to pointer addresses and capabilities, programs with data races can observe arbitrary combinations of linear addresses and capabilities that go along with them.
Plenty of exploit chains have had humbler beginnings.
This access-foo-through-pointer-to-bar scenario can't happen in Java. It can't happen in CHERI. It can happen in Fil-C. Yes, T1 at one time had a capability on foo, but the programmer intent is clearly to mutate only bar, and Fil-C allows an execution that mutates foo instead.
Elsewhere, you've claimed such executions cannot be exploited. I am skeptical of this claim given previous exploits that began by the camel poking its nose through similarly innocuous-seeming holes in the tent.
Fil-C cannot fully protect C programs from exploitable memory corruption caused by violations of the C virtual machine. 99.9% of practical ones? Sure! Fil-C is good stuff. But there are holes (not only here, but for arenas, intra-object corruption, etc.), and these are holes that safe Rust prevents. Fil-C and Rust rules prohibit different (but mostly overlapping) classes of exploit.
Could you define Fil-C's behavior as "memory safety"? Sure. You can define words to mean anything. You cannot, however, define Fil-C as something that just deletes the security implications of bugs in existing C programs. Does it mostly achieve this goal? Sure. Does it supply comprehensive coverage? No! It's a hardening tool, not a panacea, and it would behoove you to represent it as the useful tool it is, not magic pixie dust that makes C safe.
Please stop ruining a tool as good as Fil-C by claiming it does things it does not and cannot.
> In Go, you can bypass all protections and corrupt all of memory if you race on structs (I think).
This is my understanding as well. I'm glad we agree, opinions on Fil-C memory model counting as "safe" aside, that Go is awful.
> if (p == bar)
It is not in scope of memory safety to make sure that logic not related to memory accesses works as you expected.
In Fil-C, the integer pointer value (the intval) is not trusted. You could get it wrong with things more sexy than races (integer overflows or just plain bad math). Fil-C just guarantees that your accesses obey the capability model, which is true in your example - the only object the program can access is whatever object the capability you loaded points to.
You’re being disingenuously imprecise when you use this framing:
> access-foo-through-pointer-to-bar
In fact, you can only access foo if bar’s capability referred to foo, and that can only happen if the thing being raced on (the pointer in shared memory) had a prior store to it that had foo’s capability.
Hence, this isn’t an arbitrary memory access. This is a memory access that obeys the capability model. It’s a memory access that would have been possible even if the program had no race.
> Please stop ruining a tool as good as Fil-C by claiming it does things it does not and cannot.
You are mischaracterizing the issue to make it seem like it’s a memory safety issue, when it’s not. I think that is doing more damage than anything I have said
This logic is related to memory accesses. This is not an issue that can occur in systems that provide the memory-safety guarantees that the words "memory safe" usually name.
You can't escape failure to meet a public definition of a term by adopting a private definition. The English language has a term for this pattern.
> You’re being disingenuously imprecise
You are the one being imprecise. You claim that because T1 could access foo at some time in the past, it's okay for a subsequent execution to access foo when the program text limits the access to bar. The temporal history is irrelevant. You are taking language that applies to inter-actor access control and applying it to invalid execution detection.
If T1 and T2 were, say, mutually untrusting actors over a network, that T1 had a capability at one point would be a valid defense. We're talking about a totally different scenario, not an object-capability security system, but preventing attackers turning illegal C into various kinds of exploit, e.g. EOP, information disclosure, and so on.
It does not matter that T1 used to have the capability and could have used it. We're talking about an attacker using an exploit to make T1 do his bidding, not T1 itself being a hostile actor running in a sandbox.
You can't just define the problem away. Real-world C has these races. They've been exploitable. Your system doesn't close them.
> Hence, this isn’t an arbitrary memory access.
Not arbitrary, true. Irrelevant. It's a cross-object access that violates programmer expectations and is likely exploitable.
> This is a memory access that obeys the capability model
You're using the English words that denote a strong guarantee in the security community and using them to describe your weaker system.
> I think that is doing more damage than anything I have said
Damage to what? You can't fix this problem in Fil-C without introducing atomics to every globally-visible store path, so you're going around the internet trying to play definition games to define it out of existence. The only damage here is to people who think Fil-C provides stronger mitigations than it does.
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.
“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)
Fil-C: Combining the ergonomics of C with the performance of Python!
It is inconvenient to start using it as it comes with basically nothing out of the box, but this is irrelevant after a short time using it.
And it’s fun to write new code in.
If I'm understanding correctly, it's this general syscall function that's safe in Fil-C.
fil-c runs on linux.
what if linux ran on fil-c?
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.
(Left as an exercise for the reader)
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"
Thus anyone that identifies themselves with the programming language they work with, gets dragged into a us vs them discussion.
On the other hand statically checking bounds for (dynamic and non) arrays is very very hard in the general case.
(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.)
(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.)
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")
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.
Dependently types languages and model checking do exist. They come with tradeoffs, but this is also true for Rust.
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.
me: It does dynamic bounds checking.
Rustaceans: But... for XYZ ... it doesn't...
Sigh.
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.
If, of course, you redefined words so that adding run-time checks counts as "statically preventing all UB", then this applies to Fil-C just as well! You can not have it both ways.
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.
Again: I'm not disputing the fact that the rust index operator does a dynamic bounds check.
A new language designed around memory safety, such as Rust, can reject large and important classes of memory-safety bugs at compile-time. Rust does not catch everything at compile-time, like bounds checks and RefCell, which are checked at run-time(, and the problems due to unsafe and C interop like explained in the presentation.)
Still, IMO the comparison becomes a bit apples vs pears when bragging about how much more memory-safe Fil-C is than Rust. It would have been helpful to explain this important difference about run-time vs compile-time.
Very cool and useful anyway.
Rust has compile time checks to enforce some safety. In comparison Fil-C is a lot more memory safe, it's more comparable to being a software implementation of CHERI.
Unfortunately it comes with downsides: runtime performance, runtime enforcement, granular safety (the safety is around allocations).
It also comes with huge upsides: you can run C/C++ with little to no code changes. Imagine compiling nginx and the associated system libraries with Fil-C, the performance hit is probably acceptable and now the web server is memory safe.
Rust probably provides enough memory safety (even if it is not complete safety) in most circumstances though.
Sure, Fil-C prevents the bug from possibly being exploited, which is a huge improvement. But crashing can be a DoS attack, and if running a mission-critical system, it might not be an acceptable outcome.
I just feel the already very good presentation could have been made much better if it had put more weight on explaining these trade-offs.
The whole point of memory safety is that bugs can not be exploited. A memory safe program does not mean a crash safe program.
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.
Now, by pointing only at memory aligned to 4 bytes, the way a 32-bit integer would be on a typical machine, the bottom two bits of the pointer are always zero. So, we ask for such 4-byte aligned pointers when allocating memory to point at, most allocators actually never give out smaller alignments anyway because they cause problems in C but we've told the allocator our requirement so even if it could it won't give out unaligned pointers.
Finally we need to re-arrange our pointer so that those two zero bits are in the right place to put a UTF-8 continuation marker. In Rust this is legal safe code, the map_addr method on a pointer is allowed to twiddle with a pointer's address bits (on typical platforms, all of them) and so long as your transform is reversible (which this one is) it will work correctly when you twiddle the pointer back and dereference that pointer in unsafe Rust.
He seems to be the main developer of Fil-C: https://github.com/pizlonator/fil-c although there are other contributors.
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?
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.
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.
One such example,
https://www.microsoft.com/en-us/research/publication/safe-to...
Why might I write unsafe Rust? To do something not particularly fancy. A use case covered by normal safe Fil-C. Why might I write unsafe Fil-C (compiler) code? To improve compiler or runtime performance or add new platforms, etc. Analogous to wanting to work on Rust's IR or borrow checker.
Very different use-cases.
"Blocks" are relevant as they are how you express programs, algorithms, etc., that you need in order to get something done. Lots of data structures in Rust have a little unsafe somewhere. Users will typically depend on some "specialist" crate author to write them, but it exists, and is a necessary part of practical Rust programming. The pool of unsafety is open and by necessity growing.
In Fil-C, no such specialists are needed, and the pool of unsafety is closed and fixed, no matter what programs, algorithms, data structures you use.
This is not a trivial distinction I think. I think it's easy to acknowledge, especially given its (current) performance cost.
https://x.com/filpizlo/status/2053351119095791995
And "Rust let’s you corrupt memory / Fil-C doesn’t.":
https://x.com/filpizlo/status/2079253367587737841
These are false claims. The following compiles and runs fine with Fil-C, happily corrupting memory in a way that a safer language like Rust would not allow:
His claims about Fil-C being safer than Rust seem to hinge on Fil-C's ability to make safe-er an entire application stack, compared to a hypothetical Rust application that links to C dependencies which (currently) can't be compiled with Fil-C. This is true as far as it goes, but it's a far cry from justifying a blanket statement like "Fil-C is safer than Rust".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.
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
P.S. Also fan of your work. Competition is always good.
I don’t think Rust users can be trivialized into the “two kinds” that you list, and if one of the kinds is “app developer” then I bet you there are apps where Fil-C’s value proposition is exactly right, except just the fact that Fil-C is so new and immature. Say you want to ship a native UI. Using GTK from fil-C is fantastic.
And I specifically disagree with:
> 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.
When I write new code in Fil-C, I just lean into the GC all the way, which makes programming in C and C++ so much nicer. I don’t ref count, I don’t use smart pointers, I don’t free and I don’t delete. It makes these languages so much nicer!
Also, Fil-C’s guarantee that it will panic on OOB or if a race goes badly means I spend basically zero time debugging memory safety issues. The reliability of the failures totally changes the dev experience for the better.
That’s subjective obviously. Some folks swear by type systems like Rust’s to catch as many issues as possible. That’s just not how I roll.
All of that said - the reason to use rust and not Fil-C is performance and memory usage. Fil-C isn’t there yet, except for maybe a small handful of cases (like BLAKE3 and xzutils, where the overhead is basically zero for some reason … we’ll probably because I did a lot of compiler opts and sometimes you get lucky and they sort of all hit)
Yeah, I like it. I also like not caring about dangling pointers, UAF, and other things, not even because of security. These are just bugs that are not fun to debug.
But I get the appeal of the freedom of C++. Especially if you deal with FFI and low level stuff, Rust either forces you to write safe wrappers (good investment long-term, but not fun to do), or just use lots of unsafe, in which case there's no benefit.
This, and there's also cargo which is convenient.
> When I write new code in Fil-C, I just lean into the GC all the way, which makes programming in C and C++ so much nicer.
Then it's no longer C/C++. More like C++/CLI maybe. And it probably gets tricky with FFI. Anyway, keep it up on making the world a safer place :)
Sure, GC's are nice which is why so many langs have them, but that removes the deterministic allocation performance which most C/C++ programmers want (and many times need). Why not just use something like Go then? You have a much richer stdlib available out of the box.
Fil-C as currently implemented does not guarantee panics on unsafe accesses due to races. You dodge the problem by using a private definition of safety under data race that permits program executions nobody would expect.
The worst that can happen in a race is that you read or write an object that would have been accessible even in the absence of races.
The thing that makes races hard to debug in C or C++ is memory corruption; that doesn’t happen in Fil-C
Why?
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.
Exceptions came to be during the 1990's, as the C++ARM to C++98 standardisation process was taking place.
Naturally during the early days C++ compilers lacked exceptions, as CFront was introduced without them.
Then we had the C folks that were migrating to C++.
When C++ compilers finally supported exception, compilers vendors introduced the non standard switch to disable them, so that existing code could still compile with the expected behaviour.
The standard itself does not acknowledge this as allowed.
This feature was naturally misused by the anti-exception folks, same applies to how RTTI came to be.
So noexcept is a way to be able to write code to appease both camps, while surfacing what variant was chosen by the programmer.
From my point of view allowing disabling exceptions in first place was a mistake, those folks should have moved back to C, or fixed their code.
Yes. This is why unchecked exceptions are annoying. Checked exceptions are a „failed experiment“ anyway.
A result type (or even Go‘s err) with forced error handling meaningfully increases the robustness of a program in my experience.
Unchecked exceptions lead to unhandled exceptions at runtime. We've all seen screens with Java programs running with tons of exceptions in the log, or worse, that crash with unhandled exceptions. This is the result of the unchecked exceptions mess, which is why I won't use languages that use them for routine error handling for anything more than trivial programs.
Exception handling is just annoying from a syntax perspective. Also checked Exceptions have the problem that they bubble up types that a different layer shouldn’t even be aware of due to exception chaining (cause of a cause etc), unless you carefully re-throw them, which nobody did.
Lower ceremony errors are just better to deal with.
I haven't written any Java since it has gained those, so I suspect there might be other aspects making it awkward. And it still would lack exhaustiveness checking that e.g. Rust has.
https://smallcultfollowing.com/babysteps/blog/2024/05/02/unw...
First people use unsafe poorly-designed languages for writing critical software (already worse). Then someone invents a hack making code 4x slower and consuming 2x more memory to "fix" safety issues (much worse).