Not that there is anything wrong with Rust if you are ready to pay the price of having so much low level things to deal with.
Rust has a pretty darn good ecosystem too btw. chumsky for parsing, rowan for syntax trees, salsa for incremental computation, miette for errors, inkwell/walrus for code generation.
> I tend to prefer hand written parsers, either via a combinator library or fully manually.
That’s common with people used to languages which provide poor parser generators.
I don’t know of many production compilers that use parser generators
If you know the language, and you have a bunch of users, and you are writing a parser for it, by all means, write a parser by hand and give it the best error recovery that you can muster. If you are developing a language and want to do a bunch of experiments, it pays dividends to use a parser generator. And then there is the whole space of DSLs and mini-languages you encounter, where beautiful error messages are a nice-to-have, but you would rather ship a generated parser and move on to more important work.
It’s easy to focus on compilers from the perspective of familiar languages like writing compilers for Rust or for OCaml, but you may end up writing a compiler that gets used by a much smaller number of people, for smaller tasks.
I don't mean to blame you for that; you are answering a question about something that you did not say. But I find it frustrating that Mehnir seems to be cited as this fantastic cornerstone of the OCaml ecosystem when I haven't been presented with a good example of how it's better than any other parser generator. Not just my parent comment (who also decided to cast aspersions on my knowledge), but others in the OCaml community too.
My main take on the “which language do I write my compiler in” conversation is that some of the various code transformation passes are just more convenient to write in a language which makes it easy to use both mutation and garbage collection, which puts OCaml above both Rust and Haskell. I think parsing is an easier problem to solve in the first place.
I already told you that it has full support for disambiguating LR(1) grammar and still generating a parser which is easy to read. How do you want me to paste a full parser in a HN comment?
Most generators only support LALR(1) grammar which is limiting and don’t deal with corner cases as gracefully.
I get that you are hell bent on wanting Rust to prevail here but Rust will always be a subpar experience for wiring anything which doesn’t strongly benefit for its low level primitive. Rust has annoying semantics and a convoluted syntax. I can bear with that when the performances are needed but writing a compiler in it is just unnecessary pain. It’s also one of the only thing for which I would actually use Ocaml.
I have a question for you then: why is it that so many projects that are not performance bound, that are not low level systems projects, why do they use Rust and not OCaml? OCaml had a 22 year head start after all.
The vast majority of real languages have hand-rolled parsers and there is no real reason they shouldn't.
Simple to write, debug, recover from errors, provide decent error messages, unit test, integrate into build systems, IDEs etc.
I also believe that nearly all the popular compilers these days do something similar - gcc was rewritten a few years ago in this fashion because of the technical benefits I’ve listed above.
lmao
I don’t even like rust.
> RefCell<u32>
I'm not sure.
The first thing I'd suggest, as someone who's done their fair share of Rust compiler dev, is to stop using `Box` and `Ref` everywhere. Terms should not own their child terms; the idiomatic way to handle this is to use an arena and typed IDs, and pass a reference to the arena when you need access to the underlying data from the ID.
The premise of this question is incorrect: you can't not use the borrow checker in Rust. Instead, you are satisfying the borrow checker by ensuring at the point of use (deref of the ID) that there is valid data by providing a provably-live reference to the arena in which it was allocated.
In another language like C/C++, you'd just use a raw pointer, which is ergonomically-nice, but there's no guarantee that it actually points to a valid object. Rust forces you toward a solution that is guaranteed to be safe, and logically makes sense when you think about it -- nodes in a graph should not own other nodes in the graph; instead, they should be owned by the graph itself, and other nodes should simply hold references to each other. This is how you tend to implement safe and efficient graphs in C++ anyways, the only difference being that in Rust your references are indices, whereas in C++ your references are raw pointers.
Edit: Either I remembered wrong or it's changed. Now it uses some kind of custom wrapper around Box: https://github.com/rust-lang/rust/blob/master/compiler/rustc... So actually, the Rust compiler itself uses owned children in its AST!
https://play.rust-lang.org/?version=stable&mode=debug&editio...
If current top of heap + allocation size > buffer size, allocate an extra buffer for the arena. Save the current top in a temp variable. Add the allocation size to the top Return the temp variable.
It's fast, and the amortized memory overhead per allocation is near zero because you don't need to track allocation sizes or free lists, since you only free the whole arena at once (one or more buffers).
It's ideal for anything where the lifetime of allocations is known to be roughly the same. E.g. a data structure you'll free all at once.
EDIT: this was a really interesting read!