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.<p>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.
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.<p>Adding Fil-C-like runtime checks to Rust is definitely an interesting direction.
> 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.<p>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.
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.
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.
Okay, let me know when I can call process_vm_writev or ptrace and have Fil-C verify safety properties on the result.
In Rust, there are safe syscall wrappers, and then there's the unsafe general `syscall` function: <a href="https://docs.rs/libc/latest/libc/fn.syscall.html" rel="nofollow">https://docs.rs/libc/latest/libc/fn.syscall.html</a>.<p>If I'm understanding correctly, it's this general syscall function that's safe in Fil-C.
Custom libc does not call to system libc.<p>“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).<p>User libc calls to the Fil-C runtime, which filters syscalls, and those filtered syscalls are made via yolo libc.<p>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)
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.
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.
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.<p>Fil-C: Combining the ergonomics of C with the performance of Python!
indeed, the ideas could be used more widely.<p>fil-c runs on linux.<p>what if linux ran on fil-c?
Do you want your kernel to be 2x slower and use 4x as much memory?
I was under the impression that fil-c's memory management (gc?) wouldn't work on a kernel? Be sweet if you could, ofc
what if fil-c could draw pelicans?
also: why not use fil-c to improve cython, and the ffi.
If you want a slow, garbage collected language in the Python ecosystem, why not just write Python?
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.
> 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.<p>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.<p>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"
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.
Arguably the most important memory safety property, i.e. bounds checking for dynamic arrays, is also not statically checked in Rust.
How <i>would</i> 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.<p>(I'm separately skeptical that it's <i>the</i> 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.)
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
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.
> But the original claim is that it is all statically checked in Rust<p>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.<p>> 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.<p>You're couching the part where it can't be done with full generality <i>or</i> can be done with full generality, but with punishing semantics. The appropriate comparison here is with other normal general purpose compiled languages.
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.<p>Dependently types languages and model checking do exist. They come with tradeoffs, but this is also true for Rust.
Presumably, they're talking about the possibility of <i>explicitly</i> 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.
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.<p>(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.)
> rather than hoping LLVM will have your back<p>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.<p>That was a key part of my article on "No-Panic Rust": <a href="https://blog.reverberate.org/2025/02/03/no-panic-rust.html" rel="nofollow">https://blog.reverberate.org/2025/02/03/no-panic-rust.html</a> ("A Dance With The Optimizer")
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.
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.
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:<p><pre><code> for i in some_arr {
println!("{i}");
}
</code></pre>
I suspect most techniques you could think of to statically avoid bounds checks are possible in rust's type system.
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.
The combination of static safety gracefully degrading to runtime safety where not provable is interesting to me
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.
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?