Hacker Newsnew | past | comments | ask | show | jobs | submit | omcnoe's commentslogin

The problem is, the ways that it's great are hard to put into words and explain for someone unfamiliar with the game. At least, not in a succinct way. At the most basic level it's just block pushing puzzles ("Sokoban").

Trying to copy is a bit harsh, but he has credited it as an inspiration for what's possible within the Sokoban format. I believe he was a playtester for SSR before it released, mid The Witness development.

The issue with Fil-C is that it's runtime memory safety. You can still write memory-unsafe code, just now it is guaranteed to crash rather than being a potential vulnerability.

Guaranteed memory safety at compile time is clearly the better approach when you care about programs that are both functionally correct and memory safe. If I'm writing something that takes untrusted user input like a web API memory safety issues still end up as denial-of-service vulns. That's better, but it's still not great.

Not to disparage the Fil-C work, but the runtime approach has limitations.


> write memory-unsafe code, just now it is guaranteed to crash

If it's guaranteed to crash, then it's memory-safe.

If you dislike that definition, then no mainstream language is memory-safe, since they all use crashes to handle out of bounds array accesses


I don't think that's a useful way of thinking about memory-safety - a C compiler that compiles any C program to `main { exit(-1); }` is completely memory-safe. It's easy to design a memory-safe language/compiler, the question is what compromises are being made to achieve it.

Other languages have runtime exceptions on out-of-bounds access, Fil-C has unrecoverable crashes. This makes it pretty unsuitable to a lot of use cases. In Go or Java (arbitrary examples) I can write a web service full of unsafe out-of-bounds array reads, any exception/panic raised is scoped to the specific malformed request and doesn't affect the overall process. A design that's impossible in Fil-C.


You need to distinguish safety properties from liveness properties.

I don't think runtime error handling is impossible in Fil-C, at least in theory. But the use cases for that are fairly limited. Most errors like this are not anticipated, and if you did encounter them then there's little or nothing you can do useful in response. Furthermore, runtime handling to continue means code changes, thus coupling to the runtime environment. All of these things are bad. It is usually acceptable to fail fast and restart, or at least report the error.

I could have made Fil-C’s panic be a C++ exception if I had thought that it was a good idea. And then you could catch it if that’s what tickled your pickle

I just don’t like that design. It’s a matter of taste


That's actually not a bad idea, since apparently it can be used for whole operating systems. But certainly, if you started doing that, any code using the exception would need to be exclusive to Fil-C to benefit from that.

Then you run into the problem of infinite loops, which nothing can prevent (sans `main { exit(-1); }` or other forms of losing turing-completeness), and are worse than crashes - at least on crashes you can quickly restart the program (something something erlang).

try-catch isn't a particularly complete solution either if you have any code outside of it (at the very least, the catch arm) or if data can get preserved across iterations that can easily get messed up if left half-updated (say, caches, poisoned mutexes, stuck-borrowed refcells) so you'll likely want a full restart to work well too, and might even prefer it sometimes.


By that token, Rust is also memory unsafe: array bounds checks and stack overflow are runtime checks.

Why are you talking like this is black and white? Many things being compile time checkable is better than no things being compile time checkable. The existence of some thing in rust that can only be checked at runtime does not somehow make all the compile time checks that are possible irrelevant.

(Also I think the commenter you're replying to just worded their comment innacurately, code that crashes instead of violating memory safety is memory safe, a compilation error would just have been more useful than a runtime crash in most cases)


There are several ways to safely provide array bounds check hints to the Rust compiler, in-fact there's a whole cookbook. But for many cases, yep, runtime check.

Rust also has run-time crash checks in the form of run-time array bounds checks that panic. So let us not pretend that Rust strictly catches everything at compile-time.

It’s true that, assuming all things equal, compile-time checks are better than run-time. I love Rust. But Rust is only practical for a subset of correct programs. Rust is terrible for things like games where Rust simply can not prove at compile-time that usage is correct. And inability to prove correctness does NOT imply incorrectness.

I love Rust. I use it as much as I can. But it’s not the one true solution to all things.


Not trying to be a Rust advocate and I actually don't work in it personally.

But Rust provides both checked alternatives to indexed reads/writes (compile time safe returning Option<_>), and an exception recovery mechanism for out-of-bounds unsafe read/write. Fil-C only has one choice which is "crash immediately".


What makes you think that one can not add an explicit bound check in C?

It's trickier than it looks because C has mutable aliases. So, in C our bounds check might itself be a data race! Make sure you cope

Bounds checks have nothing to do with data races. GP is right, you can add bounds checks. Either using macros or (in C++) with templates and operator overloading.

Depending on what you are doing, yes. But the statement I responded to "your only choice is crash" is certainly wrong.

If you can correctly add all the required explicit bounds checks in C what do you need Fil-C for?

Same reason any turing complete language needs any constructs - to help the programmer and identify/block "unsafe" constructs.

Programming languages have always been more about what they don't let you do rather than what they do - and where that lies on the spectrum of blocking "Possibly Valid" constructs vs "Possibly Invalid".


For temporal memory safety.

>And inability to prove correctness does NOT imply incorrectness.

And inability to prove incorrectness does NOT imply correctness. I think most Rust users don't understand either, because of the hype.



SPARK does static analysis (proof) of Absence of Runtime Errors (AoRTE).

Yes, but that requires eliminating aliasing and expressions with side effects?

.get() will bounds check and the compiler will optimize that away if it can prove safety at compile time. That leaves you 3 options made available in Rust:

- Explicitly unsafe

- Runtime crash

- Runtime crash w/ compile time avoidence when possible


https://play.rust-lang.org/?version=stable&mode=debug&editio...

Catch the panic & unwind, safe program execution continues. Fundamentally impossible in Fil-C.


Seems like a niche use case. If it needs code to handle, it's also not apples to apples...

It's an apple to non-existent-apple comparison. Fil-C can't handle it even with extra code because Fil-C provides no recovery mechanism.

I also don't think it's that niche a use case. It's one encountered by every web server or web client (scope exception to single connection/request). Or anything involving batch processing, something like "extract the text from these 10k PDFs on disk".


Sure, it's not implemented in Fil-C because it is very new and the point of it is to improve things without extensive rewrites.

Generally, I think one could want to recover from errors. But error recovery is something that needs to be designed in. You probably don't want to catch all errors, even in a loop handling requests for an application. If your application isn't designed to handle the same kinds of memory access issues as we're talking about here, the whole thing turns into non-existent-apples to non-existent-apples lol.


The root comment here said this:

> All this "rewrite it in rust for safety" just sounds stupid when you can compile your C program completely memory safe.

All of the points about Rust were made in that context, and they've pushed back against it successfully enough that now you're trying to argue from the other side as if it disproves their point. No one here is saying that there's no point in having safer C code or that literally everything needs to get rewritten; they're just pointing out that yes, there is a concrete advantage that something in Rust has over something in C today even with Fil-C available.


There are many concrete disadvantages to writing things in Rust too, not to mention rewriting. But you are right, these are different solutions and they thus have different characteristics.

As for your "as if it disproves their point" stuff is wrong. The fact is, the reply to a comment in a thread is not a reply to a different one. You are implicitly setting up a straw man like "See, you are saying there are NO advantages to using Rust over Fil-C" and I never said that at any point. I also didn't say that you said that there was no advantage to using Fil-C.


My point is that no one here defending Rust is trying to say that Fil-C doesn't offer anything useful or claim that Rust is better in all circumstances. When one person says "A is strictly better than B", and some people respond "Here are some cases where you'd still get benefits from B over A", coming in and saying "A is better in these other circumstances" isn't saying anything that people aren't already aware of.

>coming in and saying "A is better in these other circumstances" isn't saying anything that people aren't already aware of.

Oh so you're a psychic now too? I think all kinds of people read these threads. Most of them probably aren't as aware as you're claiming, even the ones actively commenting on the topic.


I do not know how Fil-C handles this, but it could raise a signal that one can then catch.

Reminds me of a commercial project I did for my old University department around 1994. The GUI was ambitious and written in Motif, which was a little buggy and leaked memory. So... I ended up catching any SEGVs, saving state, and restarting the process, with a short message in a popup telling the user to wait. Obviously not guaranteed, but surprisingly it mostly worked. With benefit of experience & hindsight, I should have just (considerably) simplified it: I had user-configurable dialogs creating widgets on the fly etc, none of which was really required.

For some things the just-crash is ok, like cli usage of curl

Been working on a toy compiler for fun recently.

I have ignored all the stuff about parsing theory, parser generators, custom DSL's, formal grammers etc. and instead have just been using the wonderful Megaparsec parser combinator library. I can easily follow the parsing logic, it's unambiguous (only one successful parse is possible, even if it might not be what you intended), it's easy to compose and re-use parser functions (was particularly helpful for whitespace sensitive parsing/line-fold handling), and it removes the tedious lexer/parser split you get with traditional parsing approaches.


It seems to me LL and LR parser generators are overrated, and hand-written recursive descent is best in practice. I understand why academics teach them, but not why some spend so long on different parsing techniques, nor why hobbyists who just want to compile their toy language are directed to them.

I work in PL, and from my first compiler to today, have always found recursive descent easiest, most effective (less bugs, better error diagnostics, fast enough), and intuitive. Many popular language compilers use recursive descent: I know at least C# (Roslyn) and Rust, but I believe most except Haskell (GHC) and ocaml.

The LR algorithm was simple once I learned it, and yacc-like LR (and antlr-like LL) parser generators were straightforward once I learned how to resolve conflicts. But recursive descent (at least to me) is simpler and more straightforward.

LR being more expressive than LL has never mattered. A hand-written recursive descent parser is most expressive: it has unlimited lookahead, and can modify parsed AST nodes (e.g. reordering for precedence, converting if into if-else).

The only solution that comes close is tree-sitter, because it implements GLR, provides helpful conflict messages, and provides basic IDE support (e.g. syntax highlighting) almost for free. But it’s a build dependency, while recursive descent parsers can be written in most languages with zero dependencies and minimal boilerplate.


> It seems to me LL and LR parser generators are overrated, and hand-written recursive descent is best in practice

I would now agree with that. My compiler experience was on a team that happened to have a LALR expert, Jeanne Musinski PhD, a student of Jeffrey Ullman. She invented a better error recovery for the language. Recursive descent would have been perfectly suited to the task.

> LR being more expressive than LL has never mattered.

Quite agree. One might guess that a language that needs that might be hard to program in.


Parser generators are great in Python (Lark for me) so you can iterate fast and get a runtime spec of your grammar.

A hand-written recursive descent parser is something you do later when you start to industrialize your code, to get better error messages, make the parser incremental, etc.

Bison/ANTLR are code generators, they do not fit well in that model.


I'll push back and say that the lexer/parser split is well worth it.

And the best thing about the parser combinator approach is that each is just a kind of parser, something like

  type Lexer = ParsecT e ByteString m [Token]

  type Parser = ParsecT e [Token] Expr
All the usual helper functions like many or sepBy work equally well in the lexing and parsing phases.

It really beats getting to the parentheses-interacting-with-ordering-of-division-operations stage and still having to think "have I already trimmed off the whitespace here or not?"


I am writing a whitespace sensitive parser - trimming whitespace matters because whitespace consumption is used to implement the indentation rules/constraints.

For example, doing things like passing an indentation sensitive whitespace consumer to a parser inside `many` for consuming all of an indented child block. If I split lexing/parsing I think I'd have to do things like insert indentation tokens into the stream, and end up with the same indentation logic (but instead matching on those indentation tokens) in the parser regardless.

I have found that order-of-operations is somewhat trivially solved by `makeExprParser` from `Control.Monad.Combinators.Expr`.


This is a hilarious, and also terrible, reason.

Why can't we let the FIPS people play in their own weird corner, while not compromising whole internet security for their sake? OpenSSL is too important to get distracted by a weird US-specific security standard. I'm not convinced FIPS is a path to actual computer security. Ah well it's the way the world goes I suppose.


No, only if your website abuses window.history.pushState to redirect the user to spam/ad content is it considered abuse.

There are subtle gotchas around sequential UUID compared to serial depending on where you generate the UUIDs. You can kinda only get hard sequential guarantee if you are generating them at write time on DB host itself.

But, for both Serial & db-gen’d sequential UUID you can still encounter transaction commit order surprises. I think software relying on sequential records should use some mechanism other than Id/PK to determine it. I’ve personally encountered extremely subtle bugs related to transaction commit order and sequential Id assumptions multiple times.


DB perf considerations aside, a lot of software pattern around idempotency/safe retries/horiz-scaling/distributed systems are super awkward with a serial pk because you don’t have any kind of unambiguous unique record identifier until after the DB write succeeds.

DB itself is “distributed” in that it’s running outside the services own memory in 99% of cases, in complex systems the actual DB write may be buried under multiple layers of service indirection across multiple hosts. Trying to design that correctly while also dealing with pre-write/post-write split on record id is a nightmare.


DB sequence will give you a unique ID before the transaction succeeds. If the transaction fails, there's just a gap in the IDs.

If some service that doesn't interact with the DB wants to define its own IDs, sure, but even then whatever writes to the DB can always remap that to serial IDs. I know there are use cases where that still doesn't make sense and you really need UUID PKs, but it's not the norm.


It also contains a wonderfully prescient question asked right at the end of the talk: "... the processor gonna speculate, doing some loads out of the bounds of the array, how does it work in the hardware that it doesn't crash?"

Left unanswered at the time. I believe Spectre was known but not publicly disclosed at this time.


Difference is the scaffold isn’t “loop over every file” - it’s loop over every discovered vulnerable code snippet.

If you isolate the codebase just the specific known vulnerable code up front it isn’t surprising the vulnerabilities are easy to discover. Same is true for humans.

Better models can also autonomously do the work of writing proof of concepts and testing, to autonomously reject false positives.


Consider applying for YC's Summer 2026 batch! Applications are open till May 4

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: