> Rust is the only modern language on our list with no garbage collection.
Ridiculous. `Rc` exists for one purpose: reference counted data. When all the references are gone, it will be freed. That’s garbage collection by definition.
I already knew this article had dubious judgment when they knocked C++, the daily driver for performance-obsessed game developers, down because of lack of concurrency support though.
I agree with your characterization of the article and had an initial head scratching with regard to that rust statement, in the context of the article it is correct. Rust certainly has built-in support for immutable data, it lacks what the author is calling ‘support for immutable data structures’. In particular, the author only seems to be considering support for persistent, immutable data structures, as referenced in all the functional language examples. Rust does not have any concept of an immutable list which is changed by simply adding a pointer to the unchanged part of the list to some new derived list. I think the author should have been more explicit in qualifying the immutable characteristic as persistent all the way through the article.
Arguably, the existence of Cell destroys any notion of immutability. It depends on your worldview; if an escape hatch of any kind makes immutability a lost cause, then this point has merit. But I don't see why that would be such a big deal, since the majority of the language is immutable by default.
It depends on your definition of garbage collection though, doesn’t it? People often make a distinction between reference counting and garbage collection. Even though you can use reference counting in a GC'ed system (example: CPython), in most conversations people talk about reference counting as something different from garbage collection—for example, Objective-C’s ARC is seen as a different memory management technique from GC (you can have one or the other).
I think most people would not describe Rc or std::shared_pointer to be garbage collection.
I’d say that’s a faulty distinction, especially in a review of programming languages. Rust’s lack of a mark and sweep with GC pauses while still allowing reference counting is a feature rather than a detractor, especially when half of the issues putting e.g. Java into production are GC tuning related. It’s a language criticism in the same category as marking down Java because of its object oriented nature. If you’re finding that as a fault of Java, you’ve entirely missed the idea of the language.
> Rust’s lack of a mark and sweep with GC pauses while still allowing reference counting is a feature rather than a detractor…
Gonna stop you right there, because you’re arguing about which type of memory management is BETTER. That conversation is not one that’s going to come to a conclusion on this website because outside of certain scenarios where you might be forced to choose reference counting or garbage collection, in general, it’s just a tradeoff.
Reference counting is generally slower and less accurate, garbage collection uses more memory and has poorer latency guarantees. ¯\_(ツ)_/¯
> If you’re finding that as a fault of Java, you’ve entirely missed the idea of the language.
I don’t understand what you mean by that. Java is a product of the 90s and “everything is a class, classes encapsulate mutable state, subtype polymorphism” fell out of that. It was a bit of a fad, and the fad got baked into the language.
How is reference counting slower than say, mark-and-sweep? It's not about what's better, it's about avoiding footguns. Rust (like all languages) has the capability to build a mark-and-sweep collector if you need it, but the built-in heap deallocation (Box + ownership transfer) combined with Rc means you basically don't need it where you might have in other languages. You can't accidentally get a GC pause in Rust, which is a feature. That's not to say there's no benefits to using mark-and-sweep.
> It was a bit of a fad, and the fad got baked into the language
That may be its origin story, but if you look at Java among its current contemporaries, the story is, "What would happen if we had a language where literally everything had to use object-oriented programming, including Hello World? How would it turn out?" and the answer is very popular and used in enterprises heavily. If you can't stand OOP, the notion of even entertaining Java as a viable language is a flawed premise.
> How is reference counting slower than say, mark-and-sweep?
That’s a good question! The answer is pretty interesting. It turns out that there are a number of factors. Speaking in generalities here: reference counting touches pages precisely when you are done using them (thwarting possibly multiple LRU caches), and the deallocation must run interleaved with the mutator, which is a classic slowdown. Garbage collection algorithms like mark/sweep or copying collectors can avoid writing to dead objects, can put any state needed for garbage collection in a contiguous space, and can run “all at once” so the deallocator is not constantly competing with the mutator for cache, etc.
And then there’s the fact that with reference counting you still have to increment / decrement the reference count, and even if you only incref / decref each object once, there are still faster garbage collectors out there (measured by throughput / amortized costs).
This is the explanation for the empirical observation that reference counting is generally not very fast, although there are cases where it works well.
To get back to the original point—the claim that “Rust does use garbage collection” should not be controversial. The fact that you could beat Rust into submission until a garbage collector comes out is not really a counterargument I care to entertain.
> Rust (like all languages) has the capability to build a mark-and-sweep collector if you need it, …
You would have to build a fairly primitive mark-and-sweep collector, or rely heavily on unsafe constructs. You wouldn’t be able to compete with, say, Go or Java, which insert instructions into the generated code to make concurrent garbage collection work (synchronization points).
You would probably be able to compete with implementations like CPython, which has stable pointers, reference counting, and uses mark-and-sweep to collect cycles.
> You can't accidentally get a GC pause in Rust, which is a feature.
It comes with tradeoffs. For example, Rust lets you leak memory, and allocation is slower than in GCed languages.
> …and the answer is very popular and used in enterprises heavily.
That’s selling Java short. There are a lot of reasons why Java is popular, and explaining it in terms of OOP really doesn’t do it justice.
> can avoid writing to dead objects, can put any state needed for garbage collection in a contiguous space, and can run “all at once”
You can use arena allocation when these issues are relevant (which is not that often). Generally speakingm while obligate RC (particularly ARC) is indeed lower-throughput than obligate tracing GC, Rust does not really use obligate RC.
Not to mention that Rust is a ML based language and not a C based one. I am an OCaml programmer, and was writing significant Rust code in less than a month. It makes me wonder if the author has actually used Rust at all.
In fact, I picked up Rust because the original Rust compiler was written in OCaml!
I get the need to try to put a human face on a technical issue, but, holy mackarel, batman!
'suffer', 'trillion dollar mistake' - cut the hyperbole.
Take the debate about null in the typesystem: Yes, having some information at write/compile time about whether some function may return something in the vein of 'not found' or 'not set' is quite useful. However, `null` as a reference, even though that is exactly what is usually 'said' (the null reference was a billion dollar mistake!), is something different. Typetag based typing systems can have the null reference and nevertheless provide full write-time information about it: "This variable is of a type that isnt tagged 'definitely not null' and you are passing it as an argument in a position where you are required to have this tag'".
It's language design; things are complicated. Pithy maxims such as 'suffer', 'terrible', 'biggest crime since MS-DOS', and astronomic dollar amounts aren't helping.
Can we try to put an end to this hyperbole? This very article shows where it goes: We went from 'billion dollar' mistake to a 'trillion dollar mistake' in the span of 3 paragraphs.
I'm not looking forward to reading a hackernews post about the Vigintillion Euro Mistake in the near future to describe some solution to a complicated aspect of programming that somebody disliked a little bit.
Yep, in dealing with a fair-sized Python 3.8 codebase, type annotations with "Optional [...]" (nullable) and "Tuple[..]" (multivalue) coupled with a static type checker (in PyCharm) has been enough to mitigate the worst kinds of type errors.
> Can we try to put an end to this hyperbole? This very article shows where it goes: We went from 'billion dollar' mistake to a 'trillion dollar mistake' in the span of 3 paragraphs.
Is this meant to be some self-referential joke? There are dozens of paragraphs between these two, and in both cases they refer to something else.
I think it was an excuse to use the word "vigintillion", and perhaps also sneak in a debate over whether it should mean 10^63 (as it does in US, non-French Canada, Australia, Eastern Europe, and current British English) or 10^120 (as it does in French Canada, Central Europe, and older British English).
I meant: That 'one trillion dollar mistake' article it refers to is likely using 'trillion' because the 'null is a billion dollar mistake' thing already exists, and this one blog post refers to both of these papers/memes.
I skimmed so maybe I missed it, but I saw no mention of maintainability as a metric. For me that’s HUGE. Most of a software engineer’s job is maintaining software.
Error handling is a fundamental concept that must be tought in every programming course and it is usually neglected.
> Catching exceptions is a bad way to handle errors.
I don't agree. If used properly exceptions are a very elegant way to handle extraordinary conditions and events. Of course we have to ignore the horrible mess Java created with their checked exceptions.
I realized the power of exceptions while I was reading the book UNIX Network Programming by Richard Stevents. I was stuned to see that in some examples most of the code was not implementing the logic, but handling errors. Exceptions would make such implementations very consise and easy to read and comprehend.
> Nowadays there are much better mechanisms of error handling, possible errors should be type-checked at compile-time. Languages that do not use exceptions by default will be ranked higher.
What are they? I don't like the golang aproach each function to return 2 values (result and error code) if this is what the author means.
I have to say though that I prefer a functional approach w/ Either/Result<T,E> over Exceptions now that I've done both. With both you can choose to ignore an exception and have it "pass" up to a higher level, but the functional approach is more specific - with Exceptions and how they are implemented in most languages you have to look @ the source code to see what exception(s) could be thrown.
The more I read, the more disappointed I got (I'm a C++ programmer and fully on board on it's dreadfulness, so the article started well). The idea that Typescript is only successful because it's promoted by a big company is grade-A bollocks.
Lots of problems with this superficial and cliche-ridden article... but I was particularly irked that Python was dismissed as being dynamically typed, yet Elixir was excused because of Dialyzer - first of all, what about mypy? Second of all, it's not the same thing as actual static typing. I get it - they like Elixir. But feigning objectivity here is extremely irritating (and culminates a pattern throughout the article).
It is fine - or at least only somewhat stupid - to have an arbitrary ranking of programming languages, though things like "no garbage collection is bad" is obviously domain-dependent and the author should be upfront about that. But it's extremely frustrating when people pin objective-seeming criteria on their personal preference with seemingly no hint of irony or self-awareness.
This is a terrible article. I'm not even sure the author researched this before writing it. There's straight up false information about each of the languages.
It's ironic that the author's rankings have an almost perfect inverse correlation with how much useful software has actually been built in each language. The article comes across as the Comic Book Guy of the PL world. "Worst. Language. Ever."
Maybe we should all be using ReasonML instead of Typescript, or Elixir instead of Python, or F# instead of C++. Now explain why your CPU is 1,000 times more likely to point at code written in the latter than the former. If your answer is "oh, well I'm just much smarter than the people making these decisions", then you're probably missing something.
The author doesn't seem to realize that C# has had non-nullable reference types for over a year now, though that's the least of his issues. OOP bad, FP good seems to be his biggest issue with everything. And he dinged Rust for a lack of garbage collector, without even acknowledging the reason why it intentionally doesn't have one.
Also, doesn't acknowledge that C# has multiple concurrency options, including async/await baked into the language.
Also, .NET 5 (well, C# 9) brings record types, a.k.a. immutable data structures.
The C# complaints boils down to nulls, exceptions, being object-oriented, and not being functional (the latter two of which is one issue broken into two parts, in order to better ding OOP languages and promote functional ones).
This article seems to have an intended audience of 1: the author. If you don't agree with the author's opinions on error handling, typing, memory management, etc. you might as well stop reading.
It's funny that their criticisms of C++ includes of a quote from Linus Torvalds effectively saying that C is better but they don't include C in their list of evaluations.
They say Go is the best for "systems programming" but they give it 2.5/5
They don't explain why any of their high rated languages can't be used instead of Go.
Their conclusions are:
Frontend: ReasonML
API Development: Elixir
But if someone were looking to upskill for a job in Front end / Back end web development, are these really the first languages they should be recommended to look at?
I don't understand the ritual flagellation of nulls that seems to be in vogue recently.
The null value is a necessary and critically important value: sometimes you have to represent 'nothing.' It's not particularly special as a value. It doesn't break type systems that are designed for it.
The issue seems to be that some people forget that their code might receive null values and that nulls are represented as a zero pointer which cause an exception if dereferenced.
The resulting backlash is worse than the disease: straightjacketing languages and littering them with more busywork.
I've gone the opposite direction in my work: not only can pointers be null but so can all other basic types. It's incredibly useful and a real design win.
I think the reason is the discrepancy between what you're saying (which is true) and reality:
> sometimes you have to represent 'nothing.'
Languages in which all values can /also/ be null break the "sometimes" requirement there. Not all variables/parameters should be able to represent nothing. Representing nothing isn't the issue here, I doubt anyone disagrees that that's useful (there are still Options). The fact that anything can be nothing is.
I get that, but the solution is not "enforce it with static type checking", especially when the default is non-nullable.
In my experience nullability is a dynamic constraint and it's most often just passed through various functions, until it becomes an issue some function. Static null checking then just becomes madness.
Other representable values can also can be invalid inputs to some code, but we don't implement draconian constraints throughout the code to manage that.
> […] I’ll attempt to give an objective and hopefully unbiased overview […]
Unfortunately I think the author failed here. This is extremely objective and extremely biased. But I don't think it's the author's fault, comparing languages is always going to be subjective.
The way certain languages are ranked in the article is really weird. For example, JavaScript is ranked above Java although it's dynamically typed, which the author lists as bad at the beginning. What seems to rank it somewhat high is "the ecosystem", which is kind of hard to define (did they just look at the number of NPM packages?), and for that matter, Java has a really big number of libraries as well.
Then somewhere in the middle the article just takes a major plot twist and takes on functional languages as being the solution to everything. While I personally like F#, I find it really confusing that it still gets the "like" for its "ecosystem", although the article then proceeds to explain it's "rather small".
62 comments
[ 2.5 ms ] story [ 118 ms ] threadRidiculous. `Rc` exists for one purpose: reference counted data. When all the references are gone, it will be freed. That’s garbage collection by definition.
I already knew this article had dubious judgment when they knocked C++, the daily driver for performance-obsessed game developers, down because of lack of concurrency support though.
My favorite error is also rust related :
> Rust has no built-in support for immutable data structures.
This piece is flamebait.
It depends on your definition of garbage collection though, doesn’t it? People often make a distinction between reference counting and garbage collection. Even though you can use reference counting in a GC'ed system (example: CPython), in most conversations people talk about reference counting as something different from garbage collection—for example, Objective-C’s ARC is seen as a different memory management technique from GC (you can have one or the other).
I think most people would not describe Rc or std::shared_pointer to be garbage collection.
Gonna stop you right there, because you’re arguing about which type of memory management is BETTER. That conversation is not one that’s going to come to a conclusion on this website because outside of certain scenarios where you might be forced to choose reference counting or garbage collection, in general, it’s just a tradeoff.
Reference counting is generally slower and less accurate, garbage collection uses more memory and has poorer latency guarantees. ¯\_(ツ)_/¯
> If you’re finding that as a fault of Java, you’ve entirely missed the idea of the language.
I don’t understand what you mean by that. Java is a product of the 90s and “everything is a class, classes encapsulate mutable state, subtype polymorphism” fell out of that. It was a bit of a fad, and the fad got baked into the language.
> It was a bit of a fad, and the fad got baked into the language
That may be its origin story, but if you look at Java among its current contemporaries, the story is, "What would happen if we had a language where literally everything had to use object-oriented programming, including Hello World? How would it turn out?" and the answer is very popular and used in enterprises heavily. If you can't stand OOP, the notion of even entertaining Java as a viable language is a flawed premise.
That’s a good question! The answer is pretty interesting. It turns out that there are a number of factors. Speaking in generalities here: reference counting touches pages precisely when you are done using them (thwarting possibly multiple LRU caches), and the deallocation must run interleaved with the mutator, which is a classic slowdown. Garbage collection algorithms like mark/sweep or copying collectors can avoid writing to dead objects, can put any state needed for garbage collection in a contiguous space, and can run “all at once” so the deallocator is not constantly competing with the mutator for cache, etc.
And then there’s the fact that with reference counting you still have to increment / decrement the reference count, and even if you only incref / decref each object once, there are still faster garbage collectors out there (measured by throughput / amortized costs).
This is the explanation for the empirical observation that reference counting is generally not very fast, although there are cases where it works well.
To get back to the original point—the claim that “Rust does use garbage collection” should not be controversial. The fact that you could beat Rust into submission until a garbage collector comes out is not really a counterargument I care to entertain.
> Rust (like all languages) has the capability to build a mark-and-sweep collector if you need it, …
You would have to build a fairly primitive mark-and-sweep collector, or rely heavily on unsafe constructs. You wouldn’t be able to compete with, say, Go or Java, which insert instructions into the generated code to make concurrent garbage collection work (synchronization points).
You would probably be able to compete with implementations like CPython, which has stable pointers, reference counting, and uses mark-and-sweep to collect cycles.
> You can't accidentally get a GC pause in Rust, which is a feature.
It comes with tradeoffs. For example, Rust lets you leak memory, and allocation is slower than in GCed languages.
> …and the answer is very popular and used in enterprises heavily.
That’s selling Java short. There are a lot of reasons why Java is popular, and explaining it in terms of OOP really doesn’t do it justice.
You can use arena allocation when these issues are relevant (which is not that often). Generally speakingm while obligate RC (particularly ARC) is indeed lower-throughput than obligate tracing GC, Rust does not really use obligate RC.
In fact, I picked up Rust because the original Rust compiler was written in OCaml!
'suffer', 'trillion dollar mistake' - cut the hyperbole.
Take the debate about null in the typesystem: Yes, having some information at write/compile time about whether some function may return something in the vein of 'not found' or 'not set' is quite useful. However, `null` as a reference, even though that is exactly what is usually 'said' (the null reference was a billion dollar mistake!), is something different. Typetag based typing systems can have the null reference and nevertheless provide full write-time information about it: "This variable is of a type that isnt tagged 'definitely not null' and you are passing it as an argument in a position where you are required to have this tag'".
It's language design; things are complicated. Pithy maxims such as 'suffer', 'terrible', 'biggest crime since MS-DOS', and astronomic dollar amounts aren't helping.
Can we try to put an end to this hyperbole? This very article shows where it goes: We went from 'billion dollar' mistake to a 'trillion dollar mistake' in the span of 3 paragraphs.
I'm not looking forward to reading a hackernews post about the Vigintillion Euro Mistake in the near future to describe some solution to a complicated aspect of programming that somebody disliked a little bit.
Is this meant to be some self-referential joke? There are dozens of paragraphs between these two, and in both cases they refer to something else.
> Catching exceptions is a bad way to handle errors.
I don't agree. If used properly exceptions are a very elegant way to handle extraordinary conditions and events. Of course we have to ignore the horrible mess Java created with their checked exceptions.
I realized the power of exceptions while I was reading the book UNIX Network Programming by Richard Stevents. I was stuned to see that in some examples most of the code was not implementing the logic, but handling errors. Exceptions would make such implementations very consise and easy to read and comprehend.
> Nowadays there are much better mechanisms of error handling, possible errors should be type-checked at compile-time. Languages that do not use exceptions by default will be ranked higher.
What are they? I don't like the golang aproach each function to return 2 values (result and error code) if this is what the author means.
If anything the golang approach is creating even more boilerplate, thing the author complained about more than once
x10000 across the codebaseIt is fine - or at least only somewhat stupid - to have an arbitrary ranking of programming languages, though things like "no garbage collection is bad" is obviously domain-dependent and the author should be upfront about that. But it's extremely frustrating when people pin objective-seeming criteria on their personal preference with seemingly no hint of irony or self-awareness.
Functional languages are better than OO languages.
C++ is the worst language
Elixer is the best language.
The very idea of comparing C++, Elm and Elixir is silly in itself. What a joke.
Maybe we should all be using ReasonML instead of Typescript, or Elixir instead of Python, or F# instead of C++. Now explain why your CPU is 1,000 times more likely to point at code written in the latter than the former. If your answer is "oh, well I'm just much smarter than the people making these decisions", then you're probably missing something.
Also, .NET 5 (well, C# 9) brings record types, a.k.a. immutable data structures.
The C# complaints boils down to nulls, exceptions, being object-oriented, and not being functional (the latter two of which is one issue broken into two parts, in order to better ding OOP languages and promote functional ones).
Might as well say problem solving is bad for developer productivity.
This article is yet another example of why experience does not equal expertise.
He somehow believes the latter is easier to learn. That’s hard to square with their relative popularity in the front-end world.
They say Go is the best for "systems programming" but they give it 2.5/5
They don't explain why any of their high rated languages can't be used instead of Go.
Their conclusions are:
But if someone were looking to upskill for a job in Front end / Back end web development, are these really the first languages they should be recommended to look at?> Elixir/ReasonML/React
What about the Lisp (Common Lisp, Scheme, Emacs Lisp, Clojure, etc) and Pascal (Pascal, Delphi, Ada, Oberon, etc) families?
The null value is a necessary and critically important value: sometimes you have to represent 'nothing.' It's not particularly special as a value. It doesn't break type systems that are designed for it.
The issue seems to be that some people forget that their code might receive null values and that nulls are represented as a zero pointer which cause an exception if dereferenced.
The resulting backlash is worse than the disease: straightjacketing languages and littering them with more busywork.
I've gone the opposite direction in my work: not only can pointers be null but so can all other basic types. It's incredibly useful and a real design win.
> sometimes you have to represent 'nothing.'
Languages in which all values can /also/ be null break the "sometimes" requirement there. Not all variables/parameters should be able to represent nothing. Representing nothing isn't the issue here, I doubt anyone disagrees that that's useful (there are still Options). The fact that anything can be nothing is.
In my experience nullability is a dynamic constraint and it's most often just passed through various functions, until it becomes an issue some function. Static null checking then just becomes madness.
Other representable values can also can be invalid inputs to some code, but we don't implement draconian constraints throughout the code to manage that.
Unfortunately I think the author failed here. This is extremely objective and extremely biased. But I don't think it's the author's fault, comparing languages is always going to be subjective.
I'm guessing you meant to say "This is extremely subjective and extremely biased". Am I correct?
Then somewhere in the middle the article just takes a major plot twist and takes on functional languages as being the solution to everything. While I personally like F#, I find it really confusing that it still gets the "like" for its "ecosystem", although the article then proceeds to explain it's "rather small".