http://www.jonathanturner.org/2015/11/learning-to-try-things...
tl;dr: "Result"s are like exceptions which are caught by default. You can (explicitly) propagate them upwards by using try!(...). This is nice because it means that you can tell what exceptions can occur in a block of code only using "local" information.
Correct. That's not the problem. If Rust's standard library returned Result in all cases where allocation could fail, I'd be satisfied. My primary issue is that they didn't, because Result is awkward.
Rust's designers went wrong in trying to have their cake and eat it too. They wanted to avoid exceptions and not make people care locally about errors. That's why they assert that errors just don't happen and abort if they do.
Throwing exceptions is a reasonable design choice. Returning error codes is a reasonable design choice. Pretending errors don't exist is not.
We don't and we never have.
In Rust, exceptions (panic) are used for truly exceptional situations, like programmer error (indexing beyond the end of an array, division by zero) or things that practically are not expected to happen in a recoverable way in the course of ordinary use, like malloc failure. On modern virtual memory operating systems, malloc failure is so unlikely, and in application code there's so little you could reasonably do if it happened, that it is considered be a truly exceptional case.
On the other hand, Result is used for those kinds of errors that are expected to happen in practice even with working code on reasonable systems. IO errors, errors decoding UTF-8, etc.
Right now, catching exceptions (panics) using recover() is still considered unstable. There is some work ongoing to try and work out the API to help ensure safety, by marking types based on whether they are exception-safe or not; so you can use recover() with types that are built in an exception-safe way, or you can wrap types in AssertRecoverSafe to assert that you are providing exception-safety guarantees yourself, but you can't just arbitrarily recover from panics in code that has access to arbitrary data without someone having added an annotation somewhere that they believe that the code is exception-safe. https://github.com/rust-lang/rust/issues/27719 Note that based on the latest discussion, recover() will likely be named something else involving "unwind" to be more explicit about what it's doing.
And exception safety is quite important to the Rust authors. Note that Mutex has a built-in exception safety mechanism, poisoning the mutex on panic so that other users can't accidentally access the protected resource without being aware that another thread panicked while holding it.
Now, there are times when handling memory allocation failures properly is more important, such as in embedded systems or in operating system kernels, where you don't have a virtual memory abstraction with over-provisioning. However, in those cases you couldn't use the standard library anyhow, as the standard library depends on OS support; so you might as well use alternate data types that do return Result on allocating operations.
I'm just not sure about the utility of providing a convenient way to recover from malloc failure in applications running on virtual-memory operating systems. Can you show me an example in C++ (or any other language) where this is handled properly in application code in any way that doesn't simply log and abort, in which all unwinding code in the same application also avoids allocation as it may occur while unwinding from an allocation failure, and in which these code paths are actually tested in the test suite to ensure they behave properly?
And it's for this reason that I don't think I'll be choosing Rust for any of my projects in the near future. This cavalier attitude toward memory exhaustion is not only concerning itself, but also makes me doubt the robustness and design principles of the rest of the system.
Besides, if you make exception-safe code difficult to write, nobody in practice will write it, so you'll end up with a system that's tantamount to one that just aborts. Saying that "Rust the language handled OOM just fine without stdlib!" and "we can convert OOM to panic!" is useless when these measures don't help real world code.
> In Rust, exceptions (panic) are used for truly exceptional situations
I've never accepted the argument that we need to use one error-recovery scheme for "normal" errors and another for "exceptional" ones. That kind of claim sounds reasonable, sober, and measured, but it leads to bad outcomes in every system I've seen, because the "exceptional" case in practice becomes a hard abort. A unified error handling scheme is a boon because it greatly simplified the cognitive analysis of errors.
Java is a good example of how to do right-ish. Serious errors are Throwables not derived from Exception, so normal catch blocks are unlikely to catch them. But serious errors are still exceptions (if not Exception), and all the usual language features for processing exceptions, including unwinding, stack trace recording, and chaining, operate normally.
Uniformity of error processing in Java is a great feature, and the language gets it without sacrificing the ability to distinguish between serious and expected errors. Now, I'm not arguing that Rust get checked exceptions, but I do have to insist that experience shows that you don't need two completely different error handling mechanisms (say, panic and Result) to mark problem severity.
> But I think that it wouldn't be considered a breaking change to switch from aborting to panicking if there were any kind of demand for it.
I'm not comfortable to casual changes in core runtime semantics.
> On modern virtual memory operating systems,
Are you just defining "modern" as "overcommit"? People (especially from the GNU/Linux world) constantly assert that allocation failure is rare, but I've seen allocations fail plenty of times, due to both address space exhaustion and global memory exhaustion. I don't have any firm numbers, but I haven't seen any from the abort-on-failure camp either.
> Can you show me an example in C++ (or any other language) where this is handled properly in application code in any way that doesn't simply log and abort, in which all unwinding code in the same application also avoids allocation as it may occur while unwinding from an allocation failure, and in which these code paths are actually tested in the test suite to ensure they behave properly?
SQLite [1] and NTFS [2] come to mind, as well as lots of tools I've discovered.
[1] https://www.sqlite.org/malloc.html
[2] guaranteed to make forward progress; pre-reserves all needed recovery resources; yes, I know NTFS runs in ring zero, but it's not the case that the kernel doesn't have to deal with dynamic memory allocation