Hacker News new | ask | show | jobs
by quotemstr 4 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?

5 comments

> 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.

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.

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.

One such example,

https://www.microsoft.com/en-us/research/publication/safe-to...

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.

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.
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.
Any safe system has unsafe parts. Quibbling about whether these parts are called "blocks" isn't useful. The term changes nothing.
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.

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.
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.
To be concrete and hopefully more constructive with my criticism, he's prone to saying things like "Fil-C is safer than Rust":

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:

    #include <stdint.h>
    #include <stdio.h>
    
    struct Account {
            unsigned char name[8];
            uint32_t privileged;
    };
    
    void write_name(struct Account *account, size_t index, char value)
    {
            account->name[index] = value;
    }
    
    int main(int argc, char* argv[])
    {
            struct Account acct = {
                    .name = "foobar",
                    .privileged = 42,
            };
    
            write_name(&acct, 8, 1);
    
            printf("privileged = %d\n", acct.privileged);
            return 0;
    }
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".
Mark? AwareDigital 2013?
Hey drop me an email
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?
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.

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

Let's not shy away then. What details do you disagree on?

P.S. Also fan of your work. Competition is always good.

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)

> 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 :)

> 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.

> 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.

I'm able to see how it's safer than Rust but what I like is that it works with existing C++ code; I don't think the extra safety relative to Rust is worth the performance penalty (not to mention needing to recompile every dependency) and I doubt you think that, either
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.
>I've long faulted Rust's use of Result over exceptions

Why?

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.

> 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.

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.

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.

> 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.

Agreed, I really like Rust's Result type. Go's idea is okay, but they should force you to handle the error.

    > 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.
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.

I don't understand why Result type is necessarily slower. Result that simply bubbles up is basically an exception, no?
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.
In theory, a compiler can figure out that you don't need that in some cases.
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:

https://smallcultfollowing.com/babysteps/blog/2024/05/02/unw...

Just like on Go's case, panic/recover are exceptions with bad ergonomics.
This is a vacuous statement on par with characterizing setjmp/longjmp in C as "exceptions with bad ergonomics".
Which they kind of are, and were used as implementation mechanism for C++ exceptions in the early days on UNIX compilers.
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.
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.
No, panics are not exceptions. Exceptions are a mechanism for resumable error-handling. Rust's `catch_unwind` function is a last-ditch mechanism for failure isolation (motivated by preventing UB via unwinding across FFI boundaries), not a general-purpose error recovery mechanism. I have seen many Rust programs in my day, and not a single one has ever used `catch_unwind` in the way that (say) Java or Python programmers use try/catch for error handling, so this is as true in practice as it is in theory.

> I can disable exceptions in rustc, but I can't disable the influence they have on language and library design

It's the other way around. The fact that unwinding can be (and regularly is) trivially disabled is the ultimate motivator of why panics fundamentally cannot be used for error handling, and this is what influences language and library design (ultimately demanding `Result`-based error handling).

> 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

No, this is absolutely untrue, and I'm not sure why you think this.

> 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.

No, this is also untrue, and you appear to misunderstand the purpose of destructors. Feel free to provide code if you would like to try to make a more precise argument against unwinding.

He claims the system provides memory safety under data races. Does it not?