Weird to justify this kind of language limitation when a great technology exists that obviates the need for it.
Weird to justify this kind of language limitation when a great technology exists that obviates the need for it.
There are few people using Rust who don't have experience in other languages. It's wild that you think you know about their use cases better than they do.
Your comment says "I have never worked on a problem where GCs are problematic". That is great for you, but it has little to do with the value of improving the situation for native, no runtime, performant languages.
The biggest downside of naive reference counting usually isn't the cost of reference counting itself, it's being left stuck holding the bag when you're unlucky enough to be the last deref on a giant object graph. You can move the destruction onto a separate finalizer thread like what most runtimes that use tracing GCs do, but then you end up running into CPU scheduling issues if you're not overprovisioned.
The real problem is most GCs are built with a “throughput first” mindset and determinism is bolted on.
The details of how to build a deterministic GC are beyond the scope of a HN post lol
Obviously there's a lot of problems where a GC would be a real performance issue, but I think that people will reach to "rewrite in Rust" too quickly sometimes. Manual memory management is hard, and most software engineers, sort of by definition, are "average".
Still, the point I was making is that it's possible to have garbage collectors that get very low pause times (sub millisecond in Go's case [1]). It might still be an issue for a kernel or microcontroller, I'm not suggesting we always use a GC for every project, I don't think most people would, but I am suggesting that there are a lot of projects (most projects?) that would be better off using a low-latency GC instead of trying to handle memory directly.
Even the JVM is getting better about this; there's already experimental support for a low-latency GC in some distributions of OpenJDK [2], which reportedly gets sub-millisecond pauses as well. If you have JVM support then you have access to good languages like Clojure.
Personally, I actually think I'm reasonably-ok with manually managing memory, I've cut my teeth on enough C to write code that doesn't generally crash, but I still reach for a garbage collector whenever possible. Just because I think I'm capable of doing manual memory management doesn't mean that I think it's worth it for most things; nearly everything I do touches a network at some point, which means that avoiding latency is not really possible a lot of the time.
[1] https://groups.google.com/g/golang-dev/c/Ab1sFeoZg_8 [2] https://wiki.openjdk.org/display/shenandoah/Main
Forgive some ignorance on this, but does this mean you have literally zero millisecond pause times? What kind of additional overhead/throughput does this incur? This just seems like the holy grail!
See PTC and Aicas, the two main vendors still in business.
And even if I am not a big Go fan, TinyGo and TamaGo show what is possible regarding using Go for bare metal deployments, OS like.
It isn't for lack of alternatives with nice type systems, automatic memory management and AOT tooling.
Personally I like Scala a lot, and I used it quite a lot as a hobby for 10+ years, but my impression is that it's dying (or dead) so I'm not really motivated to play with it anymore. Kotlin always felt to me like a subpar Scala, so I never enjoyed it.
There is something about Rust that makes me feel it's a smart language and make me enjoy writing it (most of the time at least, it can absolutely be frustrating at times too). But it's certainly a matter of taste before all.
What we really need is an ergonomic way to opt into different kinds of memory allocation and management. The borrow checker is great for when you need no extraneous allocation. GCs are great for many linked lists. Bump allocators are great for web template rendering with lots of temporary strings.
Call me a crank, but while a lot of people say that you don't need to go without a garbage collector for most problems, I believe the converse: that there aren't many problems where you actually need a garbage collector (if you have some other way of freeing yourself from having to manually manage your memory and manually avoid use after free and so on). I think once your mind gets adjusted to working without one and within the constraints of the borrow checker, it's really not that much of a drag, and if you really need to do something outside those bounds (or just want to ignore it and hack something together quickly) that's what Gc and Arc are for.
And honestly, in general I like the unique intersection of features that Rust offers as a language, totally independent from its performance or the presence or absence of a garbage collector at all. Even if it was garbage collected and about 2x slower, I think I would still prefer it to most of the 40 or so languages that I've tried over the years. It feels like a version of OCaml with a much better standard library and ecosystem, a much better build system, a vastly better and cleaner way of doing parametric polymorphism, and much better metaprogramming facilities in comparison.
Not only that, but I genuinely find the constraints of the borrow checker — most especially only allowing either one mutable reference or multiple read-only references at a time — to be a great help in making my code clearer and more straightforward: it essentially helps me reach a lot of the same benefits as pure functional programming (where the data flow of my application is carefully threaded and there is no spooky action at a distance) while still allowing me to have imperative programming and mutation where I need it, by just forcing me to only have one place in my code be able to mutate anything at a given time, to explicitly mark it whenever I'm giving any piece of my code mutable access to anything, and essentially preventing me from having any persistent mutable access to a data structure be held in some other data structure or portion of code that can then continue to mutate it without my explicit permission and knowledge. So why give up the uniquely productive constraints of the borrow checker, which again to reiterate give me 90% of the benefits of pure functional programming with only 50% of the annoyance, and add in all of the unnecessary bloat and non-determinism of a garbage collector, when I have never really found myself to need it?
Furthermore, Rust is significantly more performant than even very fast and systems oriented garbage collected languages with their own runtime such as Go or OCaml or Java, which has the added benefit of allowing me to not really have to worry about performance at all beyond picking reasonable algorithms, and sometimes not even then since the language is often fast enough to make brute force feasible, which paradoxically means that when I am programming in rust, this high performance systems language, it often frees me from the burden of having to worry about performance like I would in something like python. For instance, I've actually implemented programs in Python that end up being so hilariously and frustratingly slow that I eventually gave up on working on them.
Moreover, if I do have to concern myself with performance in rust, I feel like working in a language that uses RAII and doesn't wrap everything in pointers for GC by default provides a significantly clearer mental model for me of what my program is actually doing with its memory, and how my data is actually laid out, and I also typically have a clearer mental model of what CPU operations my program is actually doing as well. And in fact just having this clear mental model of what my program is actually doing helps me Implement more efficient algorithms and make cleaner and less wasteful programs in general, which I quite like.
So yes, in essence I would turn the question back on the questioner and ask why they think they need a garbage collector and a language runtime and all of this extra stuff. Yes it does make the language marginally easier and quicker to write things in, but if I am sitting down to write a large scale program or long running project then being slowed down slightly in return for increased productivity.
GC being any kind of automatic memory management, tracing GC, reference counting, mixed scheme with reference counting and cycle collectors.
Take any systems language that uses that approach to automatic resource management, coupled with ability to use stack allocation, registers and what have you, and there are very few use cases where something else is required.
Like kernels, drivers on hard real time constraints.
Even for GPUs I think shading languages are much more ergonomic than trying to fit classical languages into them.
I'm not ready to assume again that every popular GC-based language will be able to keep up with the available memory on cloud instances, let alone be able to vertically scale to a full bare metal machine. The value of Rust's tradeoffs gets bigger the higher you vertically scale, does it not?
In all seriousness though, I'd say I only have 2 real projects that needed Rust's speed, and the rest I've done is because I bring the other type safety benefits Rust brings.
[1] Trying to wean myself off the archaic “whom”.
One easy fix is using the modern 'whomst' everywhere. It has the all the faux-sophistication of 'whom' (with some extra borrowed from 'amongst' and 'whilst') while being equitably wrong to everyone rather than special groups like whom-pedant toffs and who-everywhere anarchists.
A modern "fast" GC has latencies similar to disk I/O on modern storage. We don't make a habit of randomly dropping a blocking disk I/O in the middle of a performance critical path, and disk I/O is less likely to pollute your CPU cache as a side effect than a GC. Given that performance-optimized code is routinely designed to eliminate the overhead of even context switches (e.g. thread-per-core architectures) to great benefit and GC pauses are much more expensive than a context switch even when "fast", it is difficult to develop a theory of how a GC does not have an adverse impact on performance.
You could try to make the argument that most people writing Rust don't care about performance so it doesn't matter, but that is likely to be controversial at the least.
This doesn't address the issue with GCs in high-performance systems, it just turns it into a different kind of problem.
Rust achieves correctness at scale, over time, more than most GC languages.