>What would you rather see? Exceptions
>What would you rather see? Exceptions
Go also backed into adding most of the machinery for exceptions by adding catch with unwind for panics. If you're going to have the unwinding machinery, why not have real exceptions?
Exceptions aren't so bad if you do them the way Python does, using a predefined exception hierarchy. Catching an exception catches everything in its subtree. Catching RuntimeError catches most of the things one can induce from outside the program. Python's WITH statement also has one of the few mechanisms which can gracefully handle an error on releasing a resource. Exceptions in C++, Java, and Javascript are more troublesome.
[1] https://github.com/rust-lang/rfcs/blob/master/text/1236-stab...
In any case, panics are not guaranteed to unwind or be catchable: they can be turned into aborts (yes, on the stable compiler). This means an arbitrary library using them for recoverable error handling is incorrect.
A WITH clause mechanism implies no dynamism.
Please be more specific with your complaints. Thanks.
Additionally, unless one is using explicitly typed exceptions (i.e. Java-style checked exceptions, very much unlike Python), it requires allocations, as it's not possible to statically determine the size of the error object to be returned.
Similarly, from an assurance/reliability point of view, the dynamically typed nature of non-checked exceptions is unfortunate.
(These second two are, I believe, the points I made last time we discussed Python's exceptions.)
"We'll also need a way to handle out-of-memory (OOM) conditions. The standard library calls the abort intrinsic, which just calls an illegal instruction to crash the whole program. The reason we abort and don't panic is because unwinding can cause allocations to happen, and that seems like a bad thing to do when your allocator just came back with "hey I don't have any more memory"."[1]
Allocating is definitely a big deal for a pervasive error handling! For instance, parsing a number might not succeed. Both the actual parsing and the failure cases are cheap, but not if the latter has to be indicated by allocating and unwinding and doing a pile of the dynamic checks up the stack.
Additionally, whether the language is able to operate in OOM isn't the only reason to choose an error handling scheme. However, even if it was, I don't think it applies directly to Rust: Rust-the-language (and Rust-the-core-library) can operate in an OOM condition, e.g. people run Rust code without needing a dynamic allocator at all. You are correct that some parts of the standard library don't handle OOM, but, using exceptions pervasively means that there is one error handling scheme for things that can rely on having an OS/malloc and one for things that don't need them.
> Besides, unwinding already causes allocation
Yes, and that is one reason why unwinding is not used as a pervasive error handling scheme in Rust: there are various fairly fundamental problems with it. It is one of the reasons why panics can be switched to abort.
Exceptions are not meant for "pervasive error", they are for exceptional execution paths. If an execution path is frequent enough, wether it's an "error" or not, then it should not be an exception.
I think this is the part of the argument that the "no exception camp" usually misses : there are multi type of errors (recoverable vs fatal, frequent vs unfrequent, expected vs unexpected), having exception is not about using exceptions for all of them
If exceptions are just "an option" that a library can choose when designing its interface, then you'll get a split ecosystem: a bunch of libraries that do use exceptions for error-handling, and then a separate bunch of libraries that do the same thing, but don't use exceptions for error handling, which is wasted, redundant effort.
Agreed. Point in case: Have a look at D and the discussions about their GC.
I haven't kept up much about the last year, but my general impression runs down to: If you want to use their standard library. There's gonna be garbage collection. Period. If you want to avoid it, enjoy implementing its functionality all by yourself.
With rust and exceptions you would likely not have a problem with the main ecosystem, but the same problem is bound to pop up with some big & famous library.
Edit: Similarly, exceptions are one of the reasons C++'s SG14 exists.
Rust's uses algebraic types to make things checked at compile time and uses operators like `?` to give you expressiveness while removing the overhead and annoyance of manually checking everything. It is a huge improvement.
Catchable with: https://hackage.haskell.org/package/base-4.9.1.0/docs/Contro...
If Rust is going to bill itself as a systems programming language, it needs these. I added them for my app:
extern {
#[link_name = "llvm.setjmp"]
pub fn setjmp(a: *mut i8) -> i32;
#[link_name = "llvm.longjmp"]
pub fn longjmp(a: *mut i8, b: i32) -> ();
}
It took me awhile to figure this out (and it barely works). There's a cleaner way (adding a wrapper) that is even more work.Sure, but what I meant is that most of the time userspace programs have nothing interesting they can do with the bus error (and, as I noted, standard C does not actually provide this option anyway). Regardless there are existing signal handling libraries that aren't that hard to use in Rust (you now describe one in your post), so I'm not sure why you're pointing to that as "something... that Rust lacks."
There's no way I'm aware of that setjmp / longjmp could possibly be integrated with safe Rust, but I agree that they should be available to unsafe code (in a way that doesn't require invoking llvm intrinsics).
And that's okay. There is a ton of space outside of that context, and still within the "systems" space that C and C++ could use a high quality competitor in, and Rust fits that bill nicely, in my view.
Rust the language is really composed of two almost identical subsets, "safe" one and "unsafe". Safe Rust is what you will normally see, governed by normal safety rules and abstractions. Unsafe Rust is not what you will normally see, but still governed by safety rules and abstractions, only with an escape hatch. What I feel is that safe Rust covers the higher-level subset of "systems programming" (predictable performance, strong abstraction and safety), while unsafe Rust covers the lower-level subset of "systems programming" (excellent performance, near-complete control over everything). And still they are almost identical, so that the abstraction made in unsafe Rust is usable in safe Rust, and that's ideally how it covers the entirety of "systems programming"---the only border is the abstraction itself. Of course, provided that we have enough supply for appropriate abstractions (the community is trying hard with several promising results though).
Rust programmers do like the safety guarantee of safe Rust and rarely talk about unsafe Rust, but I think unsafe Rust plays a large role in the possibility of Rust. It's much closer than the border between, say, "glue" languages and their implementation languages. We don't change the fact that we will sometimes have to bend the rules (it's probably impossible). Instead we let you bend the rules, but only when you are in the cage. And that cage is, while not immunable to every attack, really strong.
[1] The advocacy naturally advocates for something's possibility and not for something's success. So it is still correct that Rust still lacks some solutions for existing problems, though it's not inherent.
I think that's a misunderstanding of meaning. Rust's safety and unsafe are intrinsically linked. It's not that Rust is always safe, but that you have a well defined way to compartmentalize safe and unsafe portions of the program, and can use that to reason better about what's going on. In a way, talking about the safety of Rust is specifically talking about unsafe.
> then all of my carefully crafted error-handling routines would go up in flames
No just sandbox the code inside a try catch...
> pattern matching is the best method of clearly displaying intents and possible downfalls.
You are stating an opinions as a fact here.
This works fine if it's just a matter of rolling back a database transaction or releasing file handles (b/c someone else is doing all the work for you). But if you have more complicated invariants, your try catch has to handle restoring those invariants, no matter what path your code took.
The most important feature is that catch blocks are executed before the stack is unwond, providing the ability for a handler to signal back to the context where the error occurred how it should be handled.
It's sad that no other languages implement this and instead simply decide that exceptions are bad.
The code expander has an entry point which allows the caller to be informed about what variables and functions are free in a block of code. The implementation of this entry point works by intercepting warning exceptions about unbound symbols, and accumulating them in in lists, which are then returned to the caller.
This is a useful thing which reduces the need for users to write their own code walker in certain kinds of advanced macros.
This sounds like a general errors handling issue that nor exceptions nor monadic errors types can prevent.
* panic + catch_unwind, but that's special and not meant or used in practice for error handling