(no title)
adimitrov | 5 years ago
Unchecked exceptions don't tell the caller something might go wrong. Fine for Python, where strong guarantees aren't a thing anyway, but any statically typed language cannot be content with essentially adding bottom to every single type.
Checked exceptions have failed, or at least I haven't seen anybody fix their issues. They proliferate spurious exception types in interfaces. They are inflexible, as they usually can't be generic. They suck at typing error cases for higher order functions. They're big heavy and expensive, so can't be used for hot code paths. They're exceptions but more often than not you want to signal expected failure...
The list goes on...
barrkel|5 years ago
Failure modes are an abstraction violation; they're a function of implementation. That's what makes checked exceptions not work, at the end of the day. Information-carrying exceptions reveal implementation details. So a module author must decide between hiding details and wrapping everything up in module-specific exceptions that user code can't actually use to make decisions most of the time, or expose implementation details that turn into a versioning problem over time.
There's roughly two situations for error handling: near the leaf of the call tree, where you have enough context to deal with an error, and need to switch on error type and take compensating action; or near the root of the call tree, in the main loop, where you log errors and terminate requests etc. in a generic way (e.g. 500 response).
Exceptions aren't ideal for the first situation but are great for the second. Error codes are adequate for the first situation - monadic error types (Result<T>, Either) are a bit better - but suck horribly for the second, because you need to manually unwind, writing boilerplate that should be automatic.
And at the limit, error types are isomorphic to checked exceptions, with the same problems, and more - error types introduce an aggregation problem, where multiple errors need to be joined together. You can still get that with exceptions too but it usually requires parallelism.
karatinversion|5 years ago
There is actually a third case: in library code which calls other code which may fail. Take java.io.BufferedReader - to be usable, it has to be at a level of abstraction where it cannot deal with any errors the underlying Reader may throw; but the code using BufferedReader will have provided it with its underlying Reader, and will have a good idea of what errors are reasonable to expect from it.
The reason java's checked exceptions are so bad is that they cannot (or could not, before generics, and hence in most of the standard library do not) serve this use case, leading to checked exceptions that one really can't do anything with.
stouset|5 years ago
At least in Rust, Result<t> can be unwrapped and bubbled up (in the error case) with a single `?`.
karatinversion|5 years ago
joseluisq|5 years ago
indiv0|5 years ago
In Java, unchecked exceptions are similar to Rust's panics. Sure your less inclined to catch panics than you might be to catching unchecked exceptions, but you can do it in either. Though perhaps Java's Error is closer to a rust panic.
Checked exceptions are just like the Result type IMO. The discoverability of the error surface is the same, you just don't get the nice pattern matching and sum types to handle/represent it.
What I'm getting at is that I'm a fan of the way Rust does error handling, but I don't get _why_ it feels different to the way Java does it. On the surface they're the same, but I loathe the Java approach. Maybe it's just whatever the opposite of rose-tinted glasses is.
MaxBarraclough|5 years ago
https://devblogs.microsoft.com/oldnewthing/20050114-00/?p=36...
alquemist|5 years ago
renox|5 years ago
I would add if you want to be correct then the addition signature should be (IntX, IntX) -> Either<IntX,Error>. Are you going to check also the return value of every arithmetic operation?
For me potentials errors are everywhere, so exception aren't bad, even if they make writing 'exception safe' code hard. That's why I was quite interested in the Vale language which claims to improve RAII: https://vale.dev/blog/next-steps-raii
smichel17|5 years ago
1. Statically typed.
2. Unchecked exceptions by default. IE, implicit bottom on every type.
3. Optional checked exceptions. Implementation could be `checked` keyword (e.g. `checked fun foo() throws XyzError {}`), or a generic type, or something else depending on the language. In order to mark something as checked, it must catch all exceptions from unchecked values it touches.
4. The compiler infers exceptions types when possible. So, if a regular unchecked function (`fun bar() {}`) only calls checked functions (foo from above), the compiler will carry the information about what can be thrown (XyzError) up the chain, even if there is no explicit signature. This way if another function (`checked fun baz() {}`) calls bar(), then it only has to catch or declare the XyzException, rather than all exceptions generically.
Finally, and to tie this all together: as a matter of style, reserve exceptions for actual unexpected errors. To use an example from downthread, a JSON parsing library should not be throwing parsing errors, it should be returning a Maybe<Parsed, Error>. Why? Because the library cannot determine whether the error is unexpected. If you're using it to parse user input, then yes, you're going to frequently encounter invalid data, and this should be part of your normal control flow, not something that can crash your application at runtime (you may want to log the event, maybe with a stack trace, but without interrupting normal operations). On the other hand, say you're parsing JSON from your database, (also, why are storing json in your db? But bear with me..). And invalid json means data corruption, which means your db is fubar. Then, it's fine to throw, because in that case you're operating on state that you assumed to be impossible, and your program behavior is now undefined.
The key point here is that exceptions are useful as a safety valve for early termination in the case of invalid assumptions. That's a subset of all error handling. If you try to force all error handling into the same paradigm and the same level of reliability, you're going to have awkward edge cases.
smichel17|5 years ago