There's more to the world than end-user applications
though. I think your mental model is that there are two
kinds of Rust user: OS kernel writers and people who
create applications with menu bars and save buttons.
Not at all. I myself work with more types of applications than that; I work with high-reliability networked daemons, GUI applications, and web applications. What about network services that would rather begin
failing requests on overload than shut down entirely and
restart, incurring potentially big delays in the
process?
High reliability network services generally need to be distributed across multiple machines anyhow, to provide reliability against the machine going down, so they have some notion of processes that can be stopped without shutting the whole system down. If your system can't handle one of the daemons being restarted, then it has bigger problems.However, even for this case, you can handle OOM more gracefully if you change allocation failure to panic rather than abort (either by changing the default in Rust's standard library, or using a custom allocator). At that point, you can define a proper task boundary on which you catch unwinding, make sure that everything shared across that task boundary is exception safe, and recover gracefully.
What about scientific computing projects that
are happy delaying work once they've hit pre-defined
limits? I think you're suffering from a failure of
imagination.
How many of these applications use malloc failure as their backpressure mechanism against over-allocation of resources? In general, I think they have a tendency to distribute small jobs across a large cluster, balancing them based on resource utilization, and accepting that some jobs may fail for various reasons with the ability to re-run said jobs if necessary. If Rust's goal is to supplant C, it needs to be capable
of everything C is capable of doing. Arguing that
applications in general need X or Y is a canard, because
most of those applications have no specific need of the
kind of direct memory control that Rust affords.
Rust's goal is not necessarily to supplant C or C++; they are far too widely used for that ever to be realistic.The goal is to provide a reasonable, safe alternative, that offers better abstractions and greater safety, and can be used in situations that other high-level safe languages are unsuitable for.
As far as replacing C, Rust absolutely is capable of replacing C; just use #![no_core] and handle allocation failure however you want. C++'s standard library is more comparable to Rust's standard library.
To put it another way: who are you trying to satisfy? Are
you trying to compete with Go, Nim, Python, and Java and
provide high-level facilities that work most of the time,
at the cost of control, or are you trying to compete with
C and C++, which still fill an essential niche?
By appealing to arguments about the requirements of
applications in general instead of requirements of
systems programming languages specifically, you're
suggesting that the former audience is the better bet.
As an aside, when you say "you", it sounds like you may be addressing me as a member of the Rust team. I am not; I am a user of Rust, and have contributed a few small patches, but I am only speaking for myself and not anyone else.Rust is a general purpose programming language, that is designed to appeal to a wide audience, but fill needs that other high-level languages cannot, and provide safety and abstraction that C or C++ cannot.
The first audience is likely a much larger audience, and so it is worth keeping their needs in mind, while the second audience can take the most advantage of Rust's safety and performance guarantees.
That kind of targeting is sad, since one of the promises
of Rust is that its memory safety would save us from the
plague of security holes in low-level software. The
decisions the Rust project is making right now make it
less likely that Rust will be able to fully fill C and
C++'s niche.
There are many, many applications, including more than just GUI facing applications but also servers, high-performance computing, etc, written in C and C++ that do not, and do not or do not need to handle allocation failure explicitly. In fact, in this entire discussion, you still have not pointed to a single example of a C++ application that does anything other than abort on allocation failure.However, even for applications that do not need to handle allocation failure, they would be able to take advantage of type safety, memory safety, and easy, safe concurrency. You are focusing on one, small issue, and ignoring the huge swath of other issues that you run into when writing C or C++ code that can go away by using Rust.
One of the purposes of having a standard library for a
project is to be a universal resource for all users of a
language. If Rust's standard library isn't suitable for
all environments where Rust might be used (like C++'s
standard library is), then maybe it should be packaged as
a separate project, like Qt.
But C++'s standard library is not suitable for all environments in which it's used. Other examples that have already been brought up in this discussion are in kernels, embedded systems, in any code running at Google, and heck, as you mention there are third-party libraries like Qt that are widely used frequently to the exclusion of the standard library.Something like C++ or Rust's standard library cannot be used in all situations, and even in places where it could run, no general purpose standard library is ever going to satisfy all users. What Rust aims to provide is one that works best, and most naturally, for a wide variety of use cases, which includes GUI applications, web apps, network daemons, and scientific application.
Since handling allocation failure as anything but an abort is so uncommon, it chooses to avoid either of the other two options: requiring everything that allocates to return a Result, making the interfaces to every collection type much more painful to use, or having pervasive exceptions and exception handling, meaning you need to think about exception safety everywhere.
The approach that Rust takes is a moderate approach; it uses return values for those errors that pretty much any user will have to handle, and panics for truly exceptional situations that normally should lead to an abort but which you can add special handling for at task boundaries if you need to provide higher availability, which means that you limit the number of places in which exception-safety needs to be considered to just those boundaries.
At the moment, it uses aborts for allocation failure, but there's nothing inherent to the language about that, just the current implementation.
I think the main point where our opinions diverge is that I see handling memory allocation failure with anything other than an abort as much, much more rare than the extremely common cases of exceptional situations leading to much worse results in C or C++. The sheer amount of undefined behavior, the mysterious bugs caused by buffer overruns overwriting random bits of the stack, the security vulnerabilities, the bugs caused by some undefined behavior you didn't realize was there causing the optimizer do do something strange to your code, and so on.
If allocation failure causing an abort when pushing to a Vec, unless you supply a custom allocater that panics instead and implement proper panic handling, is something that you think is fatal in terms of choosing a language, why is it not fatal that one single missed buffer length check buried in one library somewhere can cause completely unrelated parts of your application to fail mysteriously? As far as appropriateness for the kinds of projects you describe, other than the greater library and tool support due to being much more mature ecosystems, I can think of very few cases in which C or C++ would be preferable to Rust; so much of their behavior on unexpected situations is so much worse than an abort.
No comments yet.