Roc rewrites the compiler in Zig
304 comments
·February 4, 2025IshKebab
Skinney
The syntax came from Elm, which got it’s syntax from Haskell (where Nix also got it from) which got its syntax from ML.
It’s a syntax that’s several decades old at this point.
It’s different, but not harder. If you learned ML first, you’d found Algol/C-like syntax equally strange.
wk_end
(ETA: speaking strictly about anonymous functions; on rereading you might be talking about the absence of parens and commas for function application.)
That's not ML syntax. Haskell got it from Miranda, I guess?
In SML you use the `fn` keyword to create an anonymous function; in Ocaml, it's `fun` instead.
throwaway17_17
I believe the `\` character for functions is original to Haskell. Miranda does not have anonymous functions as a part of the language.
needlesslygrim
Well, ML (or at least the first versions of it) used a λx • x syntax [1] for λ-abstractions, the same (excluding the use of • over .) notation as used with the Lambda Calculus, and I've always assumed \ was an ASCII stand in.
[1]: https://homepages.inf.ed.ac.uk/wadler/papers/papers-we-love/... (can be spotted on page 353)
hillcapital
They recently changed the syntax to add parens, commas and use `|arg|` for closures :)
https://github.com/roc-lang/roc/releases/tag/0.0.0-alpha2-ro...
3836293648
Are the Roc people really doing everything they can to ruin everything they had going for them? Appealing to those who know nothing and won't be willing to touch anything FP at the cost of annoying those who actually want to try it is just so stupid
darthrupert
I say this as someone who enjoys reading Rust more than Haskell or Elm -- that looks like a really bad idea for aesthetic reasons anyway. I mean if you want the syntax to look like Zig or Rust, perhaps go all the way there instead of making a kind of a mutant hybrid like this. Syntax is superficial and the semantics actually matter, but that doesn't mean the syntax can be just anything.
Is there some deeper technical reason for making such changes?
anacrolix
Boooo
trescenzi
oh wow it went from being a very clear language to looking more like a hodgepodge of a few different languages.
IshKebab
Ah great improvements! I don't know why the sibling comments are so negative; this is clearly better.
tasuki
Why? I'm used to \ meaning lambda. I understand you aren't, and that's fine. But it's just your weird opinion determined by what programming languages you've learned.
Ultimately, it's just syntax and not so important. Semantics are important.
hajile
Pretty much all of those changes look bad to me.
hajile
I feel that he got a lot of pressure from the FP community and wrote a bunch of nonsense instead of being straightforward with them.
The only relevant reason he lists is point-free, but he doesn't go far enough. Point-free very often turns into write-only balls of unmaintainable nastiness. Wanting to discourage this behavior is a perfectly reasonable position. Unfortunately, this one true argument is given the most tepid treatment of all the reasons.
Everything else doesn't hold water.
As he knows way better than most, Elm has auto-curry and has been the inspiration for several other languages getting better error messages.
Any language with higher-order functions can give a function as a result and if you haven't read the docs or checked the type, you won't expect it. He left higher-order function in, so even he doesn't really believe this complaint.
The argument about currying and pipe isn't really true. The pipe is static syntax known to the compiler at compile time. You could just decide that the left argument is applied/curried to the function before the right argument.
I particularly hate the learning curve argument. Lots of great and necessary things are hard to learn. The only question is a value judgement about if the learning is worth the payoff. I'd guess that most of the Roc users already learned about currying with a more popular FP language before every looking at Roc, so I don't think this argument really applies here (though I wouldn't really care if he still believed it wasn't worth the learning payoff for the fraction of remaining users).
To reiterate, I agree with his conclusion to exclude currying, but I wish he were more straightforward with his one good answer that would tick off a lot of FP users rather than resorting to a ton of strawman arguments.
throwaway2037
No trolling/nitpicking from me: You wrote <<The only relevant reason he lists is point-free>>. What do you mean by "point-free"... or did you write "point three" and it was auto-corrected on a mobile phone?
I also tried Googling for that term (never heard before), and I found these:
https://stackoverflow.com/questions/944446/what-is-point-free-style-in-functional-programming
https://en.wikipedia.org/wiki/Tacit_programming
If you really meant "point-free", can you tell me where in his post he mentions it? I would like to learn more.hajile
Point-free is a functional programming style where you avoid assignments and prefer to do everything as a group of nested, mapped, and composed functions. It relies heavily on partial application. Like everything, it can be good when used in moderation.
Here's a made-up JS example. I've attempted to be fair to both approaches doing it how I personally would with each.
//typical implementation
const howManyAdults = (data) => {
const adults = data
.flatMap(obj => obj.type === 'parent' ? [obj, ...obj.children] : obj)
.filter(obj => typeof obj.age === 'number')
.filter(obj => obj.age >= 18 && obj.age < 150)
adults.forEach(adult =>
console.log(`${adult.name} is an adult age ${adult.age}`)
)
return adults.length
}
//nice "point-free" implementation using helper functions
const transformData = obj => obj.type === 'parent' ? [obj, ...obj.children] : [obj]
const isAdult = obj => obj.age >= 18 && obj.age < 130
const howManyAdults = pipe(
flatMap(transformData),
filter(hasTypeOf('age', 'number'),
filter(isAdult),
logEach`${pick('name')} is an adult age ${pick('age')}`,
len,
)
//completely point-free gets crazy
const howManyAdults = pipe(
flatMap(ifElse(
compose(eq('parent'), pick('type')),
juxt([identity, pick('children']),
identity,
), []),
filter(hasTypeOf('age', 'number'),
filter(both(gte(pick('age',18), lt(pick('age'), 130)))),
logEach`${pick('name')} is an adult age ${pick('age')}`,
len,
)
I went through a point-free phase early in my career, but even with lots of practice, I can't believe that most devs ever find the third example as readable as the first or second. I'd also note that this is a trivial example and doesn't require you to track any monad wrappers either.Personally, I rather like reading the moderate middle example because it removes the boilerplate and allows me to easily follow the overall flow without getting caught up in the details. But I'll take the first example every time if it means never dealing with the third example.
syockit
It's in the FAQ. Someone else posted a link to it in the comments, check it out. And yes, it is about tacit programming.
mrkeen
It's the closest you get to 'λ' on a US keyboard.
IshKebab
So this assumes knowledge of an obscure theoretical programming language, and a dubious symbol replacement. Yeah...
aylmao
It doesn't assume it, it's syntax, you can just use it without knowing where it comes from.
It's like english. You don't need knowledge in obscure proto-germanic linguistics to use articles in your sentences. But if you want to understand why we seem to randomly attach "a" before nouns— proto-germanic linguistics has the answer (probably, I just speak english I don't know where all its syntax originates).
myaccountonhn
Functional programming is common in university curriculum. Terrifying that it can still be considered obscure.
fuzztester
IIRC, Richard explain that in one of his videos about Roc. I have seen at least a handful of them.
LAC-Tech
I believe Haskell uses it as well.
msla
> `\` for anonymous functions
A one-character ASCII rendering of the Greek lowercase letter lambda: λ
λx → x + 5
\x -> x + 5
LAC-Tech
Opposite effect one, lost interest in Roc after reading that.
If anything I don't think Haskell goes far enough the automatic currying, points free stuff. If you're going to be declarative, don't half ass it.
rtfeldman
We recently changed Roc's lambda syntax from the syntax that languages like Elm and Haskell use...
foo = \arg1, arg2 ->
body
...to this: foo = |arg1, arg2|
body
The reason for this change was that we have a new and extremely well-received language feature (landed but not yet formally announced) which results in `->` and `=>` having different meanings in the type system. This made it confusing to have `->` in the syntax for anonymous functions, because it seemed to suggest a connection with the type-level `->` that wasn't actually there.The most popular syntax that mainstream languages use today for anonymous functions is something like `(arg1, arg2) => body` but of course that has the same problem with having an arrow in it, so changing to that wouldn't have solved the problem.
Rust uses `|arg1, arg2| body` (and Ruby kinda uses it too for blocks), and we'd all had fine experiences using that syntax in Rust, so we chose it as the new lambda syntax. You can see the new syntax in the code example at the top of roc-lang.org.
zoogeny
It's nice to see Zig continuing to gain support. I have no idea why I've ended up siding with Zig in the lower languages wars.
I used to root for the D programming language but it seems to have got stuck and never gained a good ecosystem. I've disliked Rust from the first time I saw it and have never warmed up to it's particular trade offs. C feels unergonomic these days and C++ is overfull with complexity. Zig feels like a nice pragmatic middle ground.
I actually think Rust is probably perfectly suited to a number of tasks, but I feel I would default to choosing Zig unless I was certain beyond doubt that I needed specific Rust safety features.
bornfreddy
I wanted to like Rust (memory safety and performance - what's not to like?) but both of my experiments with it ended in frustration. It seemed a way too complex language for what I needed.
Recently, a coworker of mine made a great observation that made everything clear to me. I was looking for a replacement for C, but Rust is actually a replacement for C++. Totally different beast - powerful but complex. I need to see if Zig is any closer to C in spirit.
pcwalton
> I was looking for a replacement for C, but Rust is actually a replacement for C++. Totally different beast - powerful but complex.
I've seen this sentiment a lot, and I have to say it's always puzzled me. The difference between Rust and basically any other popular language is that the former has memory safety without GC†. The difference between C++ and C is that the former is a large multi-paradigm language, while the latter is a minimalist language. These are completely different axes.
There is no corresponding popular replacement for C that's more minimalist than Rust and memory safe.
† Reference counting is a form of garbage collection.
munificent
Board games and craft beers are utterly unrelated objects who have commonality on essentially no axes (except sitting on tables, I guess). And, yet, if you like one, there's a very good chance you like the other.
I think that's where the sentiment comes from. It's not that Rust is similar to C++ in terms of the actual languages and their features. It's that people who like C++ are morely likely to like Rust than people who like C are.
I would argue that C is not a minimalistic language either. There is a lot under the hood in C. But it feels small in a way that Rust and C++ don't.
I think Rust and C++ appeal to programmers who are OK with a large investment in wrapping their head around a big complex language with the expectation that they will be able to amortize that investment by being very productive in large projects over a large period of time. Maybe sometimes the language feels like trying to keep a piece of heavy duty machinery from killing you, but they're willing to wrestle with it for the power you get in return.
The people who are excited about Zig and C wants something that feels more like a hand tool that doesn't demand a lot of their attention and lets them focus on writing their code, even if the writing process is a little more manual labor in return.
tialaramex
Today all of the code I'm not paid to write is in Rust. I spent many happy years previously getting paid to write C (I have also been paid to write Java, PHP, Go and C#, and I have written probably a dozen more languages for one reason or another over the years but never as specifically a thing people were paying me to do)
I always thought C++ was a terrible idea, from way before C++ 98, I own Stroustrup's terrible book about his language, which I picked up at the same time as the revised K&R and it did nothing to change that belief, nor have subsequent standards.
However, I do have some sympathy for this sentiment about Rust being a better C++. Even though Rust and C++ have an entirely different approach to many important problems the syntax often looks similar and I think Zig manages to be less intimidating than Rust for that reason if you don't want that complexity.
Personally I had no interest in C++† and I have no serious interest in Zig.
† Ironically I ended up caring a lot more about C++ after I learned Rust, and most specifically when understanding how Rust's HashMap type works, but I didn't end up liking C++ I just ended up much better informed about it.
gens
It is not about memory safety or anything like that. It is about simplicity.
If you say "you can't do x with y in C++" you will get an "yes you can, you just use asd::dsadasd::asdadqwreqsdwerig_hfdoigbhiohrf() with weaorgoiawr flag". From what I have seen from Rust, it is similar. I don't want to fill my brain with vim bindings.. cough.. Rust ways of doing something. I just want to code my hobby game engine v7.
That said, I am happy to use software written in it. Even though the evangelists can be really annoying.
zamalek
> † Reference counting is a form of garbage collection.
I agree with Raymond Chen's take on the academic definition of GCs[1], and therefore Rust is certainly a GC'd language (because your code behaves as though memory is infinite... usually). It's probably one of the first examples of "static garbage collection" - though I'm sure someone will point out a prior example.
[1]: https://devblogs.microsoft.com/oldnewthing/20100809-00/?p=13...
defen
> There is no corresponding popular replacement for C that's more minimalist than Rust and memory safe.
In the real world, memory safety is not all-or-nothing (unless you're willing to concede that Rust is not safe either, since unsafe Rust exists). I'm working on an embedded project in Rust and I'd MUCH rather be using Zig. The only safety thing that Rust would give me that Zig does not is protection from returning pointers to stack-allocated objects (there are no dynamic allocations and no concurrency outside of extremely simple ISRs that push events onto a statically allocated queue). But in exchange I have to deal with the presence of unsafe Rust, which feels like a gigantic minefield even compared to C.
coldtea
The similarity between C++ and Rust is that both bust your balls with complexity for programming at large. And the inverse goes for C and Zig.
Those are the axes relevant to the parent in the context of their comment - not specific language semantics or core features.
bmacho
> > I was looking for a replacement for C, but Rust is actually a replacement for C++. Totally different beast - powerful but complex.
> The difference between Rust and basically any other popular language is that the former has memory safety without GC†. The difference between C++ and C is that the former is a large multi-paradigm language, while the latter is a minimalist language. These are completely different axes.
Indeed. Let one axis be the simple/multi paradigm. Let the other axis be no memory management / automatic memory management without GC / GC. This divides the plane into 6. In the simple + no memory management sits C and Zig, and in the multiparadigm + memory safe without GC segment sits C++ and Rust.
> There is no corresponding popular replacement for C that's more minimalist than Rust and memory safe.
Those are some weird requirements for "being a replacement", but it is obviously true, as you picked them such.
hansvm
It's not a perfect analogy, but if you want to put yourself in the shoes of the people making it:
1. Rust is an immensely complicated language, and it's not very composable (see the async debacle and whatnot). On the simple<->complex slider, it's smack dab on the right of the scale.
2. Ignoring any nitpicking [0], Zig is memory-safe enough in practice, placing it much closer to Rust than to C/C++ on the memory safety axis. My teammates have been using Zig for nearly a year, and the only memory safety bug was (a) caught before prod and (b) not something Rust's features would have prevented [1]. The `defer` and `errdefer` statements are excellent, and much like how you closely audit the use of `unsafe` in Rust there is only a small subset of Zig where you actually need to pull your magnifying glass out to figure out if the code has any major issues. In terms of memory issues I've cared about (not all conforming to Rust's narrow definition of memory safety), I've personally seen many more problems in Rust projects I contribute toward (only the one in Zig, plus a misunderstanding of async as I was learning the language a few years ago, many of varying severity in Rust, at this point probably more code written in Zig than Rust, 10yoe before starting with either).
With that in mind, you have C/C++ on the unsafe axis and Zig/Rust on the safe axis. The complexity axis is self-explanatory, fleshing out the analogy.
Is Zig memory-safe? No, absolutely not. Does that mean that Rust will win out for some domains? Absolutely. In practical terms though, your average senior developer will have many memory safety bugs in C/C++ and few in Zig/Rust. It's a reasonable way to compare and contrast languages.
Is it a perfect description? No, the map is not the territory. It's an analogy that helps a lot of people understand the world around them though.
[0] Even Python is simpler than Rust, and it's memory-safe. If we're limiting ourselves to systems languages, you still have a number of options like Ada and Coq. Rust is popular because it offers a certain tradeoff in the safety/performance/devex Pareto curve, and because it's had a lot of marketing. It's unique in that niche, by definition, but it's far from the only language to offer the features you explicitly stated.
[1] It was just an object pool, and the (aggregate) resetting logic wasn't solid. The objects would have passed through the borrow checker with flying colors though.
Edit: To your GC point, many parts of Rust look closer to GC than not under the hood. You don't have a GC pause, but you have object pools (sometimes falling back to kernel object pools) and a variety of allocation data structures. If RC is a GC tactic, the extra pointer increment/decrement is negligible compared to what Rust actually does to handle its objects (RC is everything Rust does, plus a counter). That's one of my primary performance complaints with the language, that interacting with a churn of small objects is both expensive and the easiest way to code. I can't trust code I see in the wild to behave reasonably by default.
anacrolix
This is actually accurate. While Rust is a great replacement for C, so is C++. But Rust has tons of extra features that C doesn't have, just like C++. Moving from C++ to Rust is a sideways move. Moving from C to Rust is definitely an increase in complexity and abstraction.
Rust is a great replacement for C++ as it fits into the same place in the stack of tools.
Go is not a C or Python replacement.
Zig is a good replacement for C.
tialaramex
> Go is not a C or Python replacement.
Google stopped writing new Python, because they found that the same engineers would produce software with similar defect rates, at a similar price and in similar time, with Go, but it would have markedly better performance, so, no more Python.
Go manages to have a nice sharp start, which means there are going to be a bunch of cases where you didn't need all the perf from C, but you did need a prompt start, so, Java was not an option but Go is fine.
In my own practice, Rust entirely replaces C, which has previously been my preferred language for many use cases.
zozbot234
> But Rust has tons of extra features that C doesn't have, just like C++.
C++ has tons of extra features over C because it's a kitchen sink language. Rust has some extra features over C because in order to support static analysis for memory safety in a practically usable manner, you need those features. In practice, there are lots of C++ features that Rust doesn't bother with, especially around templates. (Rust just has a more general macro feature instead. The C++ folks are now on track to adding a clunky "metaclass" feature which is a lot like Rust macros.)
xedrac
> Moving from C++ to Rust is a sideways move.
In what sense? Features or complexity? From a productivity/correctness perspective, Rust is a huge step up over C++.
RossBencina
> Zig is a good replacement for C.
I have never used zig, although I have looked into it. I have used C and C++ for a long time, and would like a replacement for C.
To my mind zig is nowhere near a good replacement for C. It adds way too much extra stuff (e.g. metaprogramming). A good replacement for C would have a similar simplicity and scope to C (i.e. a portable assembler) but address known issues. For example it should fix the operator precedence rules to be sane. It should close-off on undefined behavior and specify things more formally than C, and still allow low-level memory manipulation. To be even better than C it should allow stuff that would be obviously correct in assembler, but is UB or ID in C.
pjmlp
Go has plenty of bad design decisions, but not being a safer C isn't one of them.
TinyGo and TamaGo folks enjoy writing their bare metal applications on embedded and firmware.
fc417fc802
Somewhat related, even when I work with C++ I use it as "C with RAII". What I actually want is a scheme (R5RS) with manual memory management and a borrow checker. I don't know how well such a monstrosity would actually work in practice, but it's what I've convinced myself that I want.
throwawaymaths
posted yesterday:
FpUser
>"but Rust is actually a replacement for C++. "
I would disagree. C++ provides way more features than Rust and to me Rust feels way more constrained comparatively.
tialaramex
Sure, in the sense that in C++ they've got the Head Of Vecna: https://rpgmuseum.fandom.com/wiki/The_Head_of_Vecna
Many of my favourite Rust features aren't in C++. For example they don't have real Sum types, they don't have the correct move semantic, they are statement oriented rather than expression oriented and their language lacks decent built-in tooling.
But also, some of the things C++ does have are just bad and won't get fixed/ removed because they're popular. And there's no sign of it slowing down.
I think that in practice you should use Rust in most places that C++ is used today, while some of the remainder should be WUFFS, or something entirely different.
krona
> Rust is actually a replacement for C++
Last I checked, rust touted itself as a systems programming language, which C++ kinda is but mostly isn't (many C++ features just aren't appropriate or are poorly suited for systems programming).
I would never choose Rust over C++ for scientific programming, because Rust metaprogramming features are so poor.
However I'd probably choose Rust over C in many areas where C is best suited.
So to me the venn diagram of utility shows Rust to overlap with C far more than C++.
huijzer
I read a blog that called Rust a language that was aimed to be high-level but without GC.
superlopuh
My experience with Zig is that it's also a plausible replacement for C++
fuzztester
explanation needed, bro.
nerdy langnoob or noobie langnerd here. not sure which is which, cuz my parsing skills are nearly zilch. ;)
christophilus
I've played with Hare, Zig, and Odin. Odin is my favorite. It's a fair bit faster to compile (similar to Hare), and has the nicest syntax (subjectively speaking). I wish it would get more traction. Looking forward to trying Jai if it ever makes it to GA.
wolfspaw
Odin is the best (followed by Zig)
Odin has the best approach for "standard library" by blessing/vendoring immensely useful libraries
Odin also has the best approach for Vector Math with native Vector and Matrix types
tialaramex
The swizzling is pretty cool, rather special purpose but it makes sense that Ginger Bill wanted this and it's Ginger Bill's language.
Odin's "standard library" stuff is very silly, it still feels like we were just copy-pasted the lead developer's "useful stuff" directory. Bill doesn't feel there's a value to actual standard libraries, so instead here's... whatever. He insists that's not what's going on here, but that doesn't change how it feels.
smartmic
From what you describe, you might also like Odin.
zoogeny
Interestingly enough, it is one of the only "talked about" languages I have almost no experience with. Even Roc I've watched a few YouTube videos on. I've only really seen Odin mentioned on X, not even a HN post.
I suppose there is also Jai in a similar space as well, although I'm not a devotee to Jonathan Blow and I don't share much of the excitement his followers seem to have.
I do feel Zig has the current trend moving in its favor, with projects like Ghostty and Bun gaining prominence. I think Odin would need something like that to really capture attention.
dismalaf
Odin has commercial applications, basically all the JangaFX apps... But yeah, it's missing a killer open source app. Positive is it has a decent amount of batteries included.
smartmic
> I've only really seen Odin mentioned on X, not even a HN post.
But there are some: https://hn.algolia.com/?q=Odin
johnisgood
I'm not a devotee to the creator of Odin. Odin may be fine, but I'm hesitant when it comes to contributing to it. Welp. Life goes on.
baranul
It's questionable how attractive Zig really is, outside of people getting caught in the wave generated by its foundation, where there is a clear self financial benefit. A Zig language review indicates there are many questions as to its usefulness, and why it hasn't hit v1 yet, over just using C and the many available libraries of that ecosystem. Famous C programmers like Muratori, homemade hero, do not like Zig nor recommend it[1].
Rust is primarily aimed at more specific use cases, evolving around memory safety and low(er) level programming. So where people might dislike the confusing syntax and difficulty of learning either Zig or Rust (among other inconveniences), its harder to make arguments against Rust's usefulness for safety, maturity (v1 plus), or present job market popularity. Zig does not have the luxury of those bonus points.
When it comes to general-purpose programming, including for hobbyists or students, there are many other alternative languages that are arguably much more attractive, easier to use, and/or easier to learn. Golang, Vlang, Odin, Jai, C3, etc...
[1]: https://www.youtube.com/watch?v=uVVhwALd0o4 ("Language Perf and Picking A Lang Stream" from 29:50)
LAC-Tech
I don't say the "lower language wars" as being between zig and rust. I see it as being between old and new. And I'm on team new.
knighthack
I'm happy for Zig to gain support for one major reason: Zig does not engage in "woke" culture the way Rust does.
Zig seems to take a neutral, technical-first stance, while Rust has a strong focus on inclusivity, strict moderation, and social policies like with the foundation drama (which is just nonsense for a programming language).
I really wish Nim would have won the language wars though.
greener_grass
I can't help but feel like the Roc team has an attitude of "imperative programming for me, but not for thee".
And now they are doubling down on that by moving from "OCaml meets C++" to "C, the good parts"!
If FP isn't good for writing a compiler, what is it good for?
isaacvando
Roc couldn't be optimized for writing the Roc compiler without sacrificing some of its own goals. For example, Roc is completely memory-safe, but the compiler needs to do memory-unsafe things. Introducing memory-unsafety into Roc would just make it worse. Roc has excellent performance, but it will never be as fast as a systems language that allows you to do manual memory management. This is by design and is what you want for the vast majority of applications.
There are a number of new imperative features that have been (or will be) added to the language that capture a lot of the convenience of imperative languages without losing functional guarantees. Richard gave a talk about it here: https://youtu.be/42TUAKhzlRI?feature=shared.
int_19h
It still feels kinda weird. Parsers, compilers etc are traditionally considered one of the "natural" applications for functional programming languages.
pjmlp
There are plenty of compilers written in memory safe languages, including the list of self hosted ones linked from the gist describing the rewrite.
Muromec
>but the compiler needs to do memory-unsafe things
sorry, but why?
mrkeen
I am also skeptical.
A compiler is a function from source code strings to binary bytes. Writing out instructions to do memory-unsafe things is not in itself a memory-unsafe activity.
isaacvando
I believe the main reason is for achieving the best possible performance. Someone closer to the compiler could give more detail.
vitiral
It's not just the lang but the std lib too
btreecat
> If FP isn't good for writing a compiler, what is it good for?
Summing the Fibonacci sequence I guess.
incrudible
FP is bad for computing the Fibonacci series unless you have the compiler optimization to turn it into a loop (as seen in imperative languages).
To be fair, most practical FP languages have that, but I never saw the appeal for a strictly functional general purpose language. The situations where I wished one could not use imperative constructs are very domain specific.
agumonkey
other FP languages bootstrapped onto themselves IIRC
whlr
According to [1], they want to use a systems-level language for performance.
tialaramex
"The split of Rust for the compiler and Zig for the standard library has worked well so far, and there are no plans to change it."
I assume that statement will need updating.
WD-42
At this point it feel like the are just playing with languages.
pjmlp
"Unix system programming in OCaml"
chubot
That's an interesting point, and something I thought of when reading the parser combinator vs. recursive descent point
Around 2014, I did some experiments with OCaml, and liked it very much
Then I went to do lexing and parsing in OCaml, and my experience was that Python/C++ are actually better for that.
Lexing and parsing are inherently stateful, it's natural to express those algorithms imperatively. I never found parser combinators compelling, and I don't think there are many big / "real" language implementations that uses them, if any. They are probably OK for small languages and DSLs
I use regular expressions as much as possible, so it's more declarative/functional. But you still need imperative logic around them IME [1], even in the lexer, and also in the parser.
---
So yeah I think that functional languages ARE good for writing or at least prototyping compilers -- there are a lots of examples I've seen, and sometimes I'm jealous of the expressiveness
But as far as writing lexers and parsers, they don't seem like an improvement, and are probably a little worse
[1] e.g. lexer modes - https://www.oilshell.org/blog/2017/12/17.html
greener_grass
OCaml allows mutation via reference cells.
chubot
I know that, and obviously you can write a recursive descent parser in OCaml
But I'm saying there's nothing better about it than doing it in OCaml vs. C++ or Python -- it's the same or a little worse
IMW it's natural to express the interface to a lexer and parser as classes -- e.g. you peek(), eat(), lookahead(), etc.
Classes being things that control mutation
But objects in OCaml seem to be a little separate dialect: https://dev.realworldocaml.org/objects.html
When I debug a parser, I just printf the state too, and that is a little more awkward in OCaml as well. You can certainly argue it's not worse, but I have never seen anyone argue it's better.
---
Culturally, I see a lot of discussions like this, which don't really seem focused on helping people finish their parsers:
https://discuss.ocaml.org/t/why-a-handwritten-parser/7282/7
https://discuss.ocaml.org/t/good-example-of-handwritten-lexe...
I also use lexer/parser generators, and I like that there are more tools/choices available in C/Python than in OCaml.
kristoff_it
Huh? The whole point of Roc is to let you easily put both imperative and functional code in the same project through Roc's platform system.
You get to decide what part of your code should be imperative, and which should be functional.
greener_grass
So why is it a non-goal of Roc to be implemented with this approach?
coffeeaddict1
Have you not read the post? Because compile times. Rust has awful compile times. If it didn't, I'm sure the Roc team would have stayed with Rust.
munchler
As an FP fan, I agree. This is disappointing.
5-
> Zig came up with a way around this: compile directly to LLVM bitcode (LLVM has strong backwards-compatibility on its bitcode but not on its public-facing API) and then upgrades become trivial because we can keep our existing code generation the same.
i'm glad that zig helps offset what i find a rather worrying trend -- replacing protocols with libraries (and as a consequence designing new underlying protocols in view of their only being used through the vendor library).
protocols are traditionally designed to facilitate an independent implementation; in fact, many standards ratification processes require several independent implementations as a prerequisite.
libraries (or worse, frameworks) intermingle the actual api with their own design, and necessitate a single implementation.
just the other day i wanted to display a notification a linux desktop where it wasn't convenient to depend on a library (it was a game, so not a traditional application expected to have many dependencies). the protocol (there is one, wrapped by the library) is very unpleasant, but i got it working out of spite.
and of course, when there is a perfectly nice protocol available (llvm ir, in either the bitcode or text representation) why not choose it? at least on unix, where starting processes and interprocess communication is cheap. (and as an added bonus, you won't crash when llvm does.)
steveklabnik
LLVM IR is not stable, which is a prerequisite for a good protocol.
gregwebs
Go was built while waiting for C++ to compile- fast compilation was an implicit design goal.
Rust on the other hand didn’t prioritize compile times and ended up making design decisions that make faster compilation difficult to achieve. To me it’s the biggest pain point with Rust for a large code base and that seems to be the sentiment here as well.
pcwalton
Rust always cared about compile times. Much of the motivation to switch from rustboot to rustc was compile times.
(Source: I wrote much of the Rust compiler.)
kettlecorn
The Rust ecosystem has long downplayed the importance of compile times.
Many foundational crates, serde for example, contribute much more to compile times than they need to.
I spent a long time reinventing many foundational rust crates for my game engine, and I proved its possible to attain similar features in a fraction of the compile time, but it’s a losing battle to forgo most of the ecosystem.
bhansconnect
Thank you for your work! Rust is still a great language.
I think a significant portion of our pain with rust compile times is self inflicted due to the natural growth of our crate organization and stages.
I still think the rewrite in zig is the right choice for us for various reasons, but I think at least a chunk of our compile times issues are self inflicted (though this happens to any software project that grows organically and is 300k LOC)
eximius
But were any _language_ decisions discarded due to compile time concerns? I don't think anyone would claim the folks working on the rust compiler don't care.
On that note, thank you for your part! I sure enjoy your work! :)
LegionMammal978
Since at least 2018, there's been a planned upgrade to the borrow checker to permit certain patterns that it currently rejects [0]. Also since 2018, there's been an implementation of the new version (called Polonius) that can be enabled with an unstable compiler flag [1].
But now it's 2025, and the new version still hasn't been enabled on stable Rust, since the Polonius implementation is seen as far too slow for larger programs. (I'm not exactly sure how horrible it really is, I haven't looked at the numbers.) A big goal of the types team since last year has been to reimplement a faster version within rustc [2].
I'd count this as a language feature (albeit a relatively minor one) that's been greatly deferred in favor of shorter compile times.
[0] https://blog.rust-lang.org/inside-rust/2023/10/06/polonius-u...
[1] https://github.com/rust-lang/rust/pull/51133
[2] https://rust-lang.github.io/rust-project-goals/2024h2/Poloni...
o11c
Yet, the whole crate model giving up on ABI stability as a goal hurts a lot, both for performance and for sanity.
Why aren't people designing modern languages to make it easier to keep a stable ABI, rather than giving up entirely?
pcwalton
ABI stability is one of those things that seems obvious ("sane"), until you try to implement ABI-stable unboxed generics and discover that the amount of complexity you incur, as well as the performance tax, is absurd. The code bloat with the resulting "intensional type analysis" actually makes the compile time worse (and the runtime far worse) than just specializing generics in many cases.
null
null
flooow
From listening to Feldman's podcast, this doesn't really come as a surprise to me. The rigor that Rust demands seems not to jibe with his 'worse is better' approach. That coupled with the fact they already switched the stdlib from Rust to Zig. The real question I have is why he chose Rust in the first place.
bhansconnect
Zig was not ready or nearly as popular back in 2019 when the compiler was started.
Not to mention, Richard has a background mostly doing higher level programming. So jumping all the way to something like C or Zig would have been a very big step.
Sometimes you need a stepping stone to learn and figure out what you really want.
norman784
> The real question I have is why he chose Rust in the first place.
If you read the linked post carefully you will know.
> Compile times aside, the strengths and weaknesses of Rust and Zig today are much different than they were when I wrote the first line of code in Roc's Rust compiler in 2019. Back then, Rust was relatively mature and Zig was far from where it is today.
christophilus
It’s covered in TFA, but the tldr is they started when Zig was immature.
debugnik
> we also want to convert [the parser] to use recursive descent; it was originally written using parser combinators
Almost all parser combinators are recursive descent with backtracking, they just add higher-order plumbing.
I have a feeling that whatever issue they've encountered with the combinator approach could have been worked around by handwriting only a few pieces.
bhansconnect
This is more about simplicity, maintainability, and possiblity for new contributors to easily jump in and fix things. Our current parser is not fun for new contributors to learn. Also, I think parser combinators obsficate a lot of the tracking required for robust error messages.
Personally, I find parser combinators nice for small things but painful for large and robust things.
wirrbel
When I used parser combinations in rust years ago the compile times were really long. In also think it’s a strange reason to move away from rust as a language.
UncleOxidant
What is Roc? Here it is: https://www.roc-lang.org/
chambers
This decisions looks well-reasoned. It acknowledges the strengths of their current implementation, and explains why another language may serve their goals even better. I can follow along as someone interested in general software decision-making, even though I'm not particularly invested in either language.
One specific thing I like is that the motivation for this rewrite is organic, i.e., not driven by an external pressure campaign. It's refreshing to see a drama-free rewrite.
mr_00ff00
I remember reading the HN thread when Fish rewrote to Rust. A big reason because “they wanted to stay current” so they rewrote from C++ to Rust.
Someone made a joke comment how in a few years there would be tons of HN threads about rewriting Rust to Zig.
Lyngbakr
The rationale given makes perfect sense, but isn't it risky to rewrite in a language that is yet to hit 1.0?
bhansconnect
For sure, but we have a really good relationship with the zig folks and they are willing to help us out.
On top of that, zig has gotten a lot more robust and stable of the last few releases. Upgrading is getting smoother.
But yeah, it is a risk.
stevage
Much better before than after!
Lyngbakr
Why? There's the possibility that Zig will introduce breaking changes and the Roc compiler will have to be revised in light of those changes.
brokencode
I’m assuming the previous commenter thought you were referring to Roc being pre v1 and not Zig.
There are probably some risks to it. And I think that you wouldn’t want to release a Roc v1 before Zig v1 as well.
But if things are working well now, you could always stay on an older version of Zig while you rewrite things for breaking changes in a newer version.
Still potentially a pain, but Rust is post v1 and they ended up deciding to rewrite anyway, so there are no guarantees in any approach.
wiz21c
I use rust a lot but, Roc is right: compilation times are not proportional to the safety you gain. At least in my project.
Gibbon1
I had an issue where the corporate spyware thought my cross compiler was potential malware and running scans on every file access. My compile times went from a few seconds to 2 minutes. And my codebase has a dozen targets. Where building, fixing, running tests, checking in would take half and hour it now took the whole afternoon.
I think I'd rather stick with C and run static analysis tools on my code when generating release candidates than have to deal with that.
airstrike
That's hardly the language's fault
This is the first justification of not using Rust that I actually agree with. Well written.
I recommend reading Roc's FAQ too - it's got some really great points. E.g. I'm internally screaming YESSS! to this: https://www.roc-lang.org/faq.html#curried-functions
But then it has other weird features too, like they seem to be really emphasising "friendliness" (great!) but then it has weird syntax like `\` for anonymous functions (I dunno where that dumb syntax came from by Nix also uses it and it's pretty awful). Omitting brackets and commas for function calls is also a bad decision if you care about friendliness. I have yet to find a language where that doesn't make the code harder to read and understand.