|
|
|
|
|
by klodolph
1978 days ago
|
|
I think everyone has had this distinction on their mind when designing error handling in the past thirty years or so, it’s just that figuring out an ergonomic way to express it is quite hard. In some languages the distinction is between logic errors and runtime errors. In Java, checked and unchecked exceptions. In Go, err and panic. Rust also has Err() and panic!(). If you look at, say, the evolution of the “if” statement, it was a number of years before this “obvious” control structure was added to programming languages. So there might be something similarly obvious for error handling, we just haven’t figured it out yet. |
|
I do think that checked and unchecked exceptions are the right way. The issues that people have with Java's checked exceptions are mostly centered around Java's particular implementation of the concept. The biggest failure of which, IMO, is that you can't write an interface that is generic over the exception type. Also, wrapping in try {} catch {} finally {} is cumbersome. But Java is just cumbersome. In some expression-oriented language, it could be smooth. `try` could become an expression that returns a value. Or you could have syntax help like something Rust-ish: `val thing = fallible().finally { cleanUp() }?`.
That's the main reason, IMO, people don't complain quite as much about Rust's Result<T, E>, which is very much like a checked exception mechanism in spirit. The only problem with the Rust approach is that you have an extra if-statement on every single call to a fallible function, to unwrap the success/failure. If it used exceptions, the happy paths would (sometimes) be more optimized, if I understand correctly.
But some things just can't be fixed at the language level. You have to craft good error types and messages. You have to think through your happy paths as well as your sad paths. I like when languages force you to think about failure. I don't like when languages only have unchecked exceptions for all kinds of failures.