Earlier quoted context omitted.
Note that the panic you get by calling unwrap() where you shouldn't isn't a crash. It's a controlled program exit due to an unexpected condition. While yes, the panic will cause your program to stop, it will do it in a clean deterministic way (with a backtrace). Actual crashes (due to segfaults) can happen a long way from the bug that actually caused the issue, can happen intermittently and generally be a nightmare t…
I think the use of the term "crash" varies. While it's true for e.g. a C program that there are better or worse crashes, in most other modern langs with some more safety guarantees, the term "crash" is normally used for controlled program termination due to unhandled exceptions. So coming from that type of language (where you can't segfault) I'd definitely call a panic a "crash" simply because it's the analog of an u…
Haskell is a great example of how handling errors can be harmful, since it violates confluence, even though throwing errors is fine!
Confluence is the property that evaluation order doesn't change the meaning of a program, e.g. we can do:
(1 + 2) * (3 + 4)
3 * (3 + 4)
3 * 7
21
Or: (1 + 2) * (3 + 4)
(1 + 2) * 7
3 * 7
21
We could inline some function calls if we like, thanks to referential transparency; we can even evaluate "under a lambda" (i.e. evaluate the body of a function before calling it, or evaluating the branches of an `if/then/else` before picking one); regardless of which way we evaluate, if we reach an answer (i.e. don't get stuck in a loop) then it will be the same answer: (1 + 2) * (3 + 4)
(1 + 2) * (if 3 == 0 then 4 else pred 3 + inc 4)
(1 + 2) * (if 3 == 0 then 4 else pred 3 + 5)
(1 + 2) * (if 3 == 0 then 4 else 2 + 5)
(1 + 2) * (if 3 == 0 then 4 else 7)
(1 + 2) * (if False then 4 else 7)
if (1 + 2) == 0 then 0 else (if False then 4 else 7) + (pred (1 + 2) * (if False then 4 else 7))
if (1 + 2) == 0 then 0 else 7 + (pred (1 + 2) * (if False then 4 else 7))
if 3 == 0 then 0 else 7 + (pred (1 + 2) * (if False then 4 else 7))
if 3 == 0 then 0 else 7 + (pred (1 + 2) * 7)
if False then 0 else 7 + (pred (1 + 2) * 7)
if False then 0 else 7 + (pred 3 * 7)
7 + (pred 3 * 7)
7 + (2 * 7)
7 + 14
21
Exception handlers break this, since we can write expressions like: try (head [42, Exception1, Exception2])
catch Exception1 -> 1
Exception2 -> 2
We can evaluate this one way: try (head [42, throw Exception1, throw Exception2])
catch Exception1 -> 1
Exception2 -> 2
try 42
catch Exception1 -> 1
Exception2 -> 2
42
Or another way: try (head [42, throw Exception1, throw Exception2])
catch Exception1 -> 1
Exception2 -> 2
try throw Exception1
catch Exception1 -> 1
Exception2 -> 2
1
Or another way: try (head [42, throw Exception1, throw Exception2])
catch Exception1 -> 1
Exception2 -> 2
try throw Exception2
catch Exception1 -> 1
Exception2 -> 2
2
This gives 3 different answers. Note that the throwing itself doesn't cause this problem, because we treat an "unhandled exception" as not getting an answer (equivalent to an infinite loop).When I first grokked this it was quite enlightening: adding features to a language can make it less useful. It's not that certain features (like throwing or catching exceptions) are "good" or "bad", but that we must think of languages as a whole, and knowing that some things aren't possible (like observing evaluation order) can be just as useful as allowing more things. This contrasts strongly with the tendency of languages to accumulate features over time, especially when the major justification is often "we should have it because they do" :)
There's also a nice discussion on errors vs exceptions at https://wiki.haskell.org/Error_vs._Exception