"Of course you can argue that Rust recently adopted language specification, but if you take a second to learn more about it you’ll find out that it comes essentially from outside."
It is worth noting that the Ferrocene specification is made for the certification of the Ferrocene compiler as its sole purpose. It is neither intended nor suitable to implement a compiler based on it.
Disclaimer: I'm one of the managing directors at Ferrous Systems, so biased.
The FLS was written with that specific intend in mind, but that mostly limits the scope of the spec that Ferrous Systems is/was interested in maintaining. We were also not interested in adopting large scale contributions on parts that Ferrous Systems doesn't need - we're a small outfit after all, 20ish people, and we don't have the capacity to maintain a full specification.
However, that's no fundamental barrier that limits the specification per se - now that the Rust project is adopting it, others can contribute and pick up maintenance slack. We'll certainly continue to contribute to the things that we are interested in seeing, but if someone wants to build a compiler based on the spec, they can contribute the parts needed.
On the upside: The spec in its current shape is good enough to certify the compiler and I know people have started building other things on the FLS. Structurally, the tooling and the content is fine - so I believe it can serve as a good nucleus for the Rust languages spec.
As for the "strange" aftertaste in the process of adopting the FLS that's mentioned in the article:
I don't believe that the Foundation/Project adopting the Ferrocene Language Specification is bad. While Ferrous Systems as a company holds no official position in the project, we consider ourselves very much part of the project. We have people that head working groups, one of our employees was in the governing council, my brother and co-founder is founding member of the Foundation. Rust-Analyzer is maintained by my esteemed colleague Lukas. We draw on the
project - after all we certified the projects rustc as we found it. We contribute to the project as part of the work we do. We started writing a spec because no one had written one, and specifically *we* had a need. And that's essentially how things work in open source: We scratch our itch - and in the process lift the tide for everyone.
The project taking some time to figure out what the project wants is not surprising: They have their needs, we have ours, and they often overlap but they're not identical. We're not the rust project. The project is not Ferrous Systems.
I'm not familiar at all with the Ferrocene specification, but in general I do wonder if the ISO C/C++/Fortran (singling these out as I'm somewhat familiar with them, not saying there aren't other languages with similar spec processes) process of a prose spec is really the best way to go in this day and age? Those languages certainly have their historical reasons for the specs being the way they are, but I'm not convinced it's the only right way of doing a spec if starting from scratch today.
Say, what about something like a formal machine-parsable spec and/or a testsuite that describes the way the language should behave, rather than a prose description that then each compiler writer interprets to the best of their ability?
A language standard without a reference implementation or blessed test suite can be a real problem due to ambiguity, contradiction, and omissions. When there are existing implementations of a language that are expected to keep up with an evolving standard that doesn't have these things, you can end up with a messy situation like Fortran has, in which the portability of code across compilers becomes a pragmatic job that's not related much to standard conformance.
Wasm sounds right up your alley! From my understanding the Wasm spec is formally specified and they just adopted tooling to generate prose/mechanically-checkable proofs/etc. from the formal spec [0].
You can not really write a formal spec without having prose first. It would be nice to have a formal spec for C, and C is one of the languages where this is feasible (and also partially done by various people).
> You can not really write a formal spec without having prose first.
The WebAssembly folks might have done that? From the Wasm SpecTec blog post I linked in a sister comment (emphasis in original):
> In fact, the formal version [of the Wasm spec] was written long before the prose, which was then created by manually transliterating the formal rules into natural language (for some definition of “natural”).
To be fair, I'm sure there was a lot of non-formal discussion in the process of creating the formal spec but I'm not sure whether that counts as "prose" in this context.
> My criteria were subjective but simple to understand: the language should introduce new concepts (as Principia Discordia puts it, ’tis an ill wind that blows no minds) [...]
I wonder how many people share this opinion. I derive as much if not more value from the refinement of existing concepts than the introduction of new ones.
Wanting new concepts just for it's own sake is wild to me.
The whole list sounded a bit strange to me - it looks more like a list he compiled after the fact to explain why he chose Rust, rather than some criteria he had decided on before evaluating several languages. Of course, I can sympathize with wanting to use a language that tries new concepts, but there's something to be said in favor of languages like Go that make a point of only using tried and tested concepts and making sure that they work well together too...
People are rarely willing to adopt 10% improvements. Every improvement, no matter how small, needs to be taught to the larger community of users and other stakeholders; it is relatively rare for you to be the sole stakeholder, and thus the cost of adopting the improvement is rarely worth the improvement itself, endangering making improvements. Only 10x-style improvements have a good chance of widescale adoption. Thinking that you can short-circuit this by passing 10% improvements in widely adopted languages is the mark of someone who never tried to get language features like the "var" keyword adopted in multi-million-line Java codebases.
Wanting to encounter new concepts feels like a basic requirement for intellectual curiosity.
I suppose there's a difference between wanting to encounter new concepts, and wanting to find them as a part of a programming language you've decided to learn and use, though.
For me lack of language specification is not a problem and it is good that Rust only have one implementation so I don't have a headache to support multiple compilers like C++. The only problems I have with trait is lack of const function and trait upcasting, which just solved by 1.86 that released yesterday. For iterator type mismatched it does not cause much problem since I can create a dedicated generic function to handle that.
The major problem for me with Rust is deadlock. In Rust it is very easy to cause a deadlock compared to other languages. In other languages you need to think a lot before locking a mutex but in Rust you just `foo.lock().unwrap()`, which can easily cause a deadlock if you have multiple mutexes and someone in your team does not aware the lock order.
Sharing resources is always a source of deadlocks and bugs, I don’t see a feasible resolution to that in a programming language. I don’t think one gets into deadlocks more often than any other language with Rust, but you are not experiencing most other concurrency bugs, which makes it feel like deadlocks are a bigger issue.
No, you don't have data races in Rust. Deadlocks are more or less just as possible in Rust as in other languages. In fact, the 2024 edition of Rust changed something[0] to make it more difficult to hit some deadlock situations, but it's well-known that there's not much in the language to protect you from them.
Those "evangelists" would be wrong, then. Rust prevents data races by default, but doesn't prevent deadlocks by default. It is possible to use Rust's type system to statically ensure the lack of deadlocks [0], but that's not provided by default.
I should have bookmarked the HN post that said it :)
It was one of the usual "if you'd have rewritten it in rust you'd have had no problems" posts. But instead of the usual memory safety list, they also added no deadlocks to the benefits.
I think that's more a case of unnecessary pedantry. Rust "has garbage collection" in that you can opt in to the lifetime of some objects being handled by some kind of garbage collection. Rust doesn't "have garbage collection" in that Rust does not require a GC'd runtime to implement its semantics in a memory-safe manner. Two different concepts with similar terminology.
Edit: Also looking at that commenter's history I am rather skeptical they would reasonably be considered a "Rust evangelist"
As others have said, Rust's ownership model prevents data races, as it can prove that references to mutable data can only be created if there are no other references to that data. Safe Rust also prevents use-after-free, double-free, and use-uninitialized errors.
Does not prevent memory leaks or deadlocks.
It's easy to write code that deadlocks; make two shared pointers to the same mutex, then lock them both. This compiles without warnings:
let mutex_a = Arc::new(Mutex::new(0_u32));
let mutex_b = mutex_a.clone();
let a = mutex_a.lock().unwrap();
let b = mutex_b.lock().unwrap();
println!("{}", *a + *b);
You can use the parking_lot mutex implementation crate, which includes support for deadlock detection. Personally, I also try to avoid using Tokio's async mutexes.
Tokio mutexes are only useful if a lock needs to be held across suspension points. Since futures can migrate threads between suspension points and most regular mutexes on most platforms do not support being unlocked from a different thread, tokio’s mutex has a very narrow but a well defined use case.
Another use for tokio's mutexes is that they won't block a thread while waiting to acquire the lock (as awaiting a tokio mutex is itself a suspension point). For some use-cases this might not matter, though.
You could - and this is what we do - push all of your state into one large struct. Note that this has drawbacks as well and if you have a lot of reads and some very important writes, you will still get deadlocks.
Ah, this is a codec guy. That yields an unusual list of desired features. Bit-pushing predominates. Inline assembler and access to SIMD instructions is important. Speed in tight loops is essential.
Their wishlist is composability, being able to write performant code, being able to perform other low-level operations, being able to use assembly and a reasonably easy build system.
I'd argue that's not just a fairly common list of desired features but that if there's more than 1 of those desires not in your own list then there's probably better languages than Rust to consider.
> Even more annoying is that I have to duplicate the iterator code: as I often work with frames that may be flipped bottom-up I want to write code like
>
> let mut iterator = if !flipped {
> data.chunks_mut(stride)
> } else {
> data.chunks_mut(stride).rev()
> };
> for line in iterator { ... }
>
> but I can’t since objects have different type (and probably size) and there’s no realistic way to coerce them to the same interface.
For this specific case there is a relatively ergonomic solution using dynamic trait objects + temporary lifetime extensions, available since Rust 1.79:
let iterator: &mut dyn Iterator<Item = _> = if !flipped {
&mut data.into_iter()
} else {
&mut data.into_iter().rev()
};
for line in iterator { ... }
Alternatively, if this is not a feasible solution (e.g. we want to return the new iterator to an outer scope, or the overhead of dyn indirection is unacceptable in this context), one can consider using itertools::Either instead:
use itertools::Either;
fn conditionally_flip_iter<I: DoubleEndedIterator>(
data: I,
flipped: bool,
) -> impl Iterator<Item = I::Item> {
if !flipped {
Either::Left(data.into_iter())
} else {
Either::Right(data.into_iter().rev())
}
}
// ...
let mut iterator = conditionally_flip_iter(data, flipped);
for line in iterator { ... }
let mut iterator = data.chunks_mut(stride);
while let Some(line) = if !flipped { iterator.next() } else { iterator.next_back() } {
…
}
I sometimes wonder idly if the language would have been better off without a `for` loop (if `while let` had happened much earlier). `while let` is more verbose, mainly because iterator construction has to be an explicit separate line, but it’s also more amenable to alteration to make it more fit for purpose. (You can go down to `loop` and `break`, but I think this sacrifices too much in ergonomics and comprehensibility in the usual case. As `while let` also compromises, but I wonder if it’s closer to the sweet spot than commonly imagined, and `for` doesn’t actually get you so much.)
> no dick (or walrus) operator (for me it’s been a good sign that I’m not going to like the language, so far there were no false positives). This means that Go and Nim are out
Not sure where they got that Nim has a walrus operator. Maybe because the syntax resembles Python? That'd make sense.
I was not familiar with the term, so I had to look it up. It's about code like this:
a := b
the `:=` looks vaguely like a walrus. The most common reference language seems to be Python [1]. The usage is for making assignment remain an expression, so you can do stuff like
area = (width := get_width()) * (height := get_height())
or something, and have the top-level expression remain valid since the sub-expressions are assignments that remain expressions, i.e. the assigned value is also the result of the expression.
IIRC he stepped down because he thought it's a good feature and people pointlessly moaned about it. Not because he thought it's controversial and has enough.
I used it a small handful of times. But it also seems like quite a source of footguns. Many examples here: https://github.com/satwikkansal/wtfpython rely on the misuse of the walrus operator.
Go uses "==" for equality checks, "=" for assignments, and ":=" for "declaration/assignment with type inference" - so it's different from both Python (you can use ":=" as part of an expression in Go too, but you can also use "=") and Algol/Pascal (where it's used only for assignment).
In math = is used for equality and := for definition[1] which is probably the closest thing to assignment in a programming language. That is why Pascal and similar languages use = for equality only and := for assignment.
That always made more sense to me than = for assignment and more =s for different kinds of equality, but in the end this syntax discussions are always just bike-shedding.
Author and lots of commenters specifying their biggest complaint about rust and nobody has mentioned lack of async drop? That's the top of my list by far, everything else is just nits in comparison.
Certainly. One probably could writre a PhD thesis in sociology about this community given how "particular" its interactions are with the outside world.
"Of course you can argue that Rust recently adopted language specification, but if you take a second to learn more about it you’ll find out that it comes essentially from outside."
It is worth noting that the Ferrocene specification is made for the certification of the Ferrocene compiler as its sole purpose. It is neither intended nor suitable to implement a compiler based on it.
Disclaimer: I'm one of the managing directors at Ferrous Systems, so biased.
The FLS was written with that specific intend in mind, but that mostly limits the scope of the spec that Ferrous Systems is/was interested in maintaining. We were also not interested in adopting large scale contributions on parts that Ferrous Systems doesn't need - we're a small outfit after all, 20ish people, and we don't have the capacity to maintain a full specification.
However, that's no fundamental barrier that limits the specification per se - now that the Rust project is adopting it, others can contribute and pick up maintenance slack. We'll certainly continue to contribute to the things that we are interested in seeing, but if someone wants to build a compiler based on the spec, they can contribute the parts needed.
On the upside: The spec in its current shape is good enough to certify the compiler and I know people have started building other things on the FLS. Structurally, the tooling and the content is fine - so I believe it can serve as a good nucleus for the Rust languages spec.
As for the "strange" aftertaste in the process of adopting the FLS that's mentioned in the article:
I don't believe that the Foundation/Project adopting the Ferrocene Language Specification is bad. While Ferrous Systems as a company holds no official position in the project, we consider ourselves very much part of the project. We have people that head working groups, one of our employees was in the governing council, my brother and co-founder is founding member of the Foundation. Rust-Analyzer is maintained by my esteemed colleague Lukas. We draw on the project - after all we certified the projects rustc as we found it. We contribute to the project as part of the work we do. We started writing a spec because no one had written one, and specifically *we* had a need. And that's essentially how things work in open source: We scratch our itch - and in the process lift the tide for everyone. The project taking some time to figure out what the project wants is not surprising: They have their needs, we have ours, and they often overlap but they're not identical. We're not the rust project. The project is not Ferrous Systems.
I'm not familiar at all with the Ferrocene specification, but in general I do wonder if the ISO C/C++/Fortran (singling these out as I'm somewhat familiar with them, not saying there aren't other languages with similar spec processes) process of a prose spec is really the best way to go in this day and age? Those languages certainly have their historical reasons for the specs being the way they are, but I'm not convinced it's the only right way of doing a spec if starting from scratch today.
Say, what about something like a formal machine-parsable spec and/or a testsuite that describes the way the language should behave, rather than a prose description that then each compiler writer interprets to the best of their ability?
A language standard without a reference implementation or blessed test suite can be a real problem due to ambiguity, contradiction, and omissions. When there are existing implementations of a language that are expected to keep up with an evolving standard that doesn't have these things, you can end up with a messy situation like Fortran has, in which the portability of code across compilers becomes a pragmatic job that's not related much to standard conformance.
Wasm sounds right up your alley! From my understanding the Wasm spec is formally specified and they just adopted tooling to generate prose/mechanically-checkable proofs/etc. from the formal spec [0].
[0]: https://webassembly.org/news/2025-03-27-spectec/
You can not really write a formal spec without having prose first. It would be nice to have a formal spec for C, and C is one of the languages where this is feasible (and also partially done by various people).
> You can not really write a formal spec without having prose first.
The WebAssembly folks might have done that? From the Wasm SpecTec blog post I linked in a sister comment (emphasis in original):
> In fact, the formal version [of the Wasm spec] was written long before the prose, which was then created by manually transliterating the formal rules into natural language (for some definition of “natural”).
To be fair, I'm sure there was a lot of non-formal discussion in the process of creating the formal spec but I'm not sure whether that counts as "prose" in this context.
It is also clear that you can write a formal spec down before writing down prose, if your language is simple and low-level enough.
> It is neither intended nor suitable to implement a compiler based on it.
I guess you could make the same argument for C++...
(In case you're wondering, just as an example, you might start implementing garbage collection).
> My criteria were subjective but simple to understand: the language should introduce new concepts (as Principia Discordia puts it, ’tis an ill wind that blows no minds) [...]
I wonder how many people share this opinion. I derive as much if not more value from the refinement of existing concepts than the introduction of new ones.
Wanting new concepts just for it's own sake is wild to me.
The whole list sounded a bit strange to me - it looks more like a list he compiled after the fact to explain why he chose Rust, rather than some criteria he had decided on before evaluating several languages. Of course, I can sympathize with wanting to use a language that tries new concepts, but there's something to be said in favor of languages like Go that make a point of only using tried and tested concepts and making sure that they work well together too...
People are rarely willing to adopt 10% improvements. Every improvement, no matter how small, needs to be taught to the larger community of users and other stakeholders; it is relatively rare for you to be the sole stakeholder, and thus the cost of adopting the improvement is rarely worth the improvement itself, endangering making improvements. Only 10x-style improvements have a good chance of widescale adoption. Thinking that you can short-circuit this by passing 10% improvements in widely adopted languages is the mark of someone who never tried to get language features like the "var" keyword adopted in multi-million-line Java codebases.
Wanting to encounter new concepts feels like a basic requirement for intellectual curiosity.
I suppose there's a difference between wanting to encounter new concepts, and wanting to find them as a part of a programming language you've decided to learn and use, though.
For me lack of language specification is not a problem and it is good that Rust only have one implementation so I don't have a headache to support multiple compilers like C++. The only problems I have with trait is lack of const function and trait upcasting, which just solved by 1.86 that released yesterday. For iterator type mismatched it does not cause much problem since I can create a dedicated generic function to handle that.
The major problem for me with Rust is deadlock. In Rust it is very easy to cause a deadlock compared to other languages. In other languages you need to think a lot before locking a mutex but in Rust you just `foo.lock().unwrap()`, which can easily cause a deadlock if you have multiple mutexes and someone in your team does not aware the lock order.
Sharing resources is always a source of deadlocks and bugs, I don’t see a feasible resolution to that in a programming language. I don’t think one gets into deadlocks more often than any other language with Rust, but you are not experiencing most other concurrency bugs, which makes it feel like deadlocks are a bigger issue.
Wait, I've been told by "evangelists" that besides not having any security vulnerabilities, you also can't have deadlocks in Rust!
No, you don't have data races in Rust. Deadlocks are more or less just as possible in Rust as in other languages. In fact, the 2024 edition of Rust changed something[0] to make it more difficult to hit some deadlock situations, but it's well-known that there's not much in the language to protect you from them.
[0] https://doc.rust-lang.org/edition-guide/rust-2024/temporary-...
Those "evangelists" would be wrong, then. Rust prevents data races by default, but doesn't prevent deadlocks by default. It is possible to use Rust's type system to statically ensure the lack of deadlocks [0], but that's not provided by default.
[0]: e.g., https://docs.rs/lock_ordering/latest/lock_ordering
I should have bookmarked the HN post that said it :)
It was one of the usual "if you'd have rewritten it in rust you'd have had no problems" posts. But instead of the usual memory safety list, they also added no deadlocks to the benefits.
Hey now Rust is also garbage collected:
https://news.ycombinator.com/item?id=43555249#43592602
I wonder what they'll add to it by tomorrow...
I think that's more a case of unnecessary pedantry. Rust "has garbage collection" in that you can opt in to the lifetime of some objects being handled by some kind of garbage collection. Rust doesn't "have garbage collection" in that Rust does not require a GC'd runtime to implement its semantics in a memory-safe manner. Two different concepts with similar terminology.
Edit: Also looking at that commenter's history I am rather skeptical they would reasonably be considered a "Rust evangelist"
As others have said, Rust's ownership model prevents data races, as it can prove that references to mutable data can only be created if there are no other references to that data. Safe Rust also prevents use-after-free, double-free, and use-uninitialized errors. Does not prevent memory leaks or deadlocks.
It's easy to write code that deadlocks; make two shared pointers to the same mutex, then lock them both. This compiles without warnings:
You can use the parking_lot mutex implementation crate, which includes support for deadlock detection. Personally, I also try to avoid using Tokio's async mutexes.
Tokio mutexes are only useful if a lock needs to be held across suspension points. Since futures can migrate threads between suspension points and most regular mutexes on most platforms do not support being unlocked from a different thread, tokio’s mutex has a very narrow but a well defined use case.
Another use for tokio's mutexes is that they won't block a thread while waiting to acquire the lock (as awaiting a tokio mutex is itself a suspension point). For some use-cases this might not matter, though.
Could you create a type around Mutex that enforces the locking order?
Something like https://docs.rs/lock_ordering/latest/lock_ordering perhaps?
You could - and this is what we do - push all of your state into one large struct. Note that this has drawbacks as well and if you have a lot of reads and some very important writes, you will still get deadlocks.
Open source implementation > open standard every time.
C++ is from an era open source was not yet well established.
Ah, this is a codec guy. That yields an unusual list of desired features. Bit-pushing predominates. Inline assembler and access to SIMD instructions is important. Speed in tight loops is essential.
Their wishlist is composability, being able to write performant code, being able to perform other low-level operations, being able to use assembly and a reasonably easy build system.
I'd argue that's not just a fairly common list of desired features but that if there's more than 1 of those desires not in your own list then there's probably better languages than Rust to consider.
Another approach:
I sometimes wonder idly if the language would have been better off without a `for` loop (if `while let` had happened much earlier). `while let` is more verbose, mainly because iterator construction has to be an explicit separate line, but it’s also more amenable to alteration to make it more fit for purpose. (You can go down to `loop` and `break`, but I think this sacrifices too much in ergonomics and comprehensibility in the usual case. As `while let` also compromises, but I wonder if it’s closer to the sweet spot than commonly imagined, and `for` doesn’t actually get you so much.)> no dick (or walrus) operator (for me it’s been a good sign that I’m not going to like the language, so far there were no false positives). This means that Go and Nim are out
Not sure where they got that Nim has a walrus operator. Maybe because the syntax resembles Python? That'd make sense.
I was not familiar with the term, so I had to look it up. It's about code like this:
the `:=` looks vaguely like a walrus. The most common reference language seems to be Python [1]. The usage is for making assignment remain an expression, so you can do stuff like or something, and have the top-level expression remain valid since the sub-expressions are assignments that remain expressions, i.e. the assigned value is also the result of the expression.[1]: https://realpython.com/python-walrus-operator/
Its also one of the more controversial features and its introduction sparked Guido's stepping down.
Since it was introduced, I have been looking but there has been only a handful of times where I could use it to write clearer code.
I'm curious if anyone actually likes it.
edit: A quick search shows that I do use it regularly in while loops, e.g.:
IIRC he stepped down because he thought it's a good feature and people pointlessly moaned about it. Not because he thought it's controversial and has enough.
I used it a small handful of times. But it also seems like quite a source of footguns. Many examples here: https://github.com/satwikkansal/wtfpython rely on the misuse of the walrus operator.
I have written Python since 2003 and used walrus only once. It's unnecessary.
Eh, I write a lot of Python and while it's unnecessary, it can make code easier to read if not overused.
is just a tiny bit shorter than this: but if you do this enough times (if you are handling a lot of possible errors / missing data, etc.) it can definitely shorten the code.If you have to wrap it in parens, it might be shorter but it's not really clearer.
In other contexts, parens are a good indication it's time for a new line.
But I can agree something like this does look clean and clear:
python's use is the unusual one; the most common use (dating all the way back to algol) is to use it for assignment, so you can use = for equality.
https://en.wikipedia.org/wiki/Assignment_(computer_science)#...
Go uses "==" for equality checks, "=" for assignments, and ":=" for "declaration/assignment with type inference" - so it's different from both Python (you can use ":=" as part of an expression in Go too, but you can also use "=") and Algol/Pascal (where it's used only for assignment).
In math = is used for equality and := for definition[1] which is probably the closest thing to assignment in a programming language. That is why Pascal and similar languages use = for equality only and := for assignment.
That always made more sense to me than = for assignment and more =s for different kinds of equality, but in the end this syntax discussions are always just bike-shedding.
Author and lots of commenters specifying their biggest complaint about rust and nobody has mentioned lack of async drop? That's the top of my list by far, everything else is just nits in comparison.
[flagged]
I'm more annoyed by people who seem to think it's necessary to complain about Rust whenever there's a post about it.
Especially on this post, where the article is about as zeal-free as can be.
I have been annoyed by this before, but now it's mostly obvious troll or sarcastic posts.
Rust haters' cult on the other hand...
I think every language has its... people. Javascript, VB, you name it.
That said, Rust is the only language I've ever actively avoided because of its community.
Curious if you have any examples since I didn’t read any zeal from the posted essay.
What are you referring to? Pointing out ways one's language is better is not necessarily arrogance. It's just communication.
Certainly. One probably could writre a PhD thesis in sociology about this community given how "particular" its interactions are with the outside world.