4 ms·
Java and Rust are the only language that I know of that has proper handling of exceptions; mandatory declaration as part of method declaration, since exceptions
by Flundstrom2 11mo ago
Java and Rust are the only language that I know of that has proper handling of exceptions; mandatory declaration as part of method declaration, since exceptions ARE an integral part of the contract. (Yes, I consider the Result<T, E> being corresponding to exception declaration, since the return value MUST be checked prior to use of T.)
- nielsbot 11mo agoSwift user here: I have to say one of the best features of Swift is the exception handling. Which is to say, exceptions in Swift are not C++/Java/Obj-C style exceptions, but instead are a way to return an error result from a function. And Swift enforces that the error is handled. That is, a `throw` statement in Swift simply returns an `Error` value to the caller via a special return path instead of the normal result. More explicitly, a Swift function declared as: func f() throws -> T { } Could be read as func f() -> (T|any Error) { } More here: https://github.com/swiftlang/swift/blob/main/docs/ErrorHandlingRationale.md https://github.com/swiftlang/swift/blob/main/docs/ErrorHandl...
- thomasmg 11mo agoI saw that in Swift, a method can declare it throws an exception, but it doesn't (can't) declare the exception _type_. I'm not a regular user of Swift (I usually use Java - I'm not sure what other languages you are familiar with), but just thinking about it: isn't it strange that you don't know the exception type? Isn't this kind of like an untyped language, where you have to read the documentation on what a method can return? Isn't this a source of errors itself, in practise?
- mayoff 11mo agoSwift gained limited support for “typed throws” in Swift 6.0 (2024). https://github.com/swiftlang/swift-evolution/blob/main/proposals/0413-typed-throws.md https://github.com/swiftlang/swift-evolution/blob/main/propo... I say limited because the compiler doesn't (yet, as of 6.2) perform typed throw inference for closures (a closure that throws is inferred to throw `any Error`). I have personally found this sufficiently limiting that I've given up using typed throws in the few places I want to, for now.
- Someone 11mo ago> isn't it strange that you don't know the exception type? Java experience taught us that, when writing an interface, it is common not to know the exception type. You often can’t know, for example, whether an implementation can time out (e.g. because it will make network calls) or will access a database (and thus can throw RollbackException). Consequently, when implementing an interface, it is common in Java to wrap exceptions in an exception of the type declared in the interface (https://wiki.c2.com/?ExceptionTunneling https://wiki.c2.com/?ExceptionTunneling)
- magicalhippo 11mo agoWhat I liked about Bosst's error_code[1], which is part of the standard library now, is that it carties not just the error but the error category, and with it a machinery for categories to compare error_codes from other categories. So as a user you could check for a generic file_not_found error, and if the underlying library uses http it could just pass on the 404 error_code with an http_category say, and your comparison would return true. This allows you to handle very specific errors yet also allow users to handle errors in a more generic fashion in most cases. [1]: https://www.boost.org/doc/libs/latest/libs/system/doc/html/system.html https://www.boost.org/doc/libs/latest/libs/system/doc/html/s...
- tcfhgj 11mo agoWhen using a language forcing checked exceptions, you would know, wouldn't you?
- thomasmg 11mo agoYes I know Java and the challenges with exceptions there (checked vs unchecked exceptions, errors). But at least (arguably) in Java, the methods (for checked exceptions at least) declares what class the exception / exceptions is. I personally do not think wrapping exceptions in other exception types, in Java, is a major problem. In Swift, you just have "throws" without _any_ type. And so the caller has to be prepared for everything: a later version of the library might suddenly return a new type of exception. One could argue Rust is slightly better than Java, because in Rust there are no unchecked exceptions. However, in Rust there is panic, which is in a way like unchecked exceptions, which you can also catch (with panic unwinding). But at least in Rust, regular exceptions are fast.
- deleted 11mo ago[deleted]
- nielsbot 11mo agoI don't think it's strange at all--my main uses of the returned errors are 1a. yes, there was some error 1b. there was an error--throw another local error and encapsulate the caught error 2. treat result of throwing call as `nil` and handle appropriately I don't think typed throws add anything to the language. I think they will result in people wasting time pondering error types and building large error handling machines :) When I used Java, I found typed exceptions difficult to reason about and handle correctly.
- amomchilov 11mo agoTyped exceptions are unlike typed parameters or return values. They don’t just describe the interface of your function, but expose details about its implementation and constrain future changes. That’s a huge limitation when writing libraries. If you have an old function that declares that it can throw a DatabaseError, you can’t e.g. add caching to it. Adding CacheError to the list of throwable types is an API breaking change, just like changing a return type. Swift has typed errors now, but they shouldn’t be used carefully, and probably not be the default to reach for
- fakwandi_priv 11mo agoFrom what I can read Swift gives you a stack trace which is good. At the moment I’m using Go where that stack is only generated where the panic is triggered, which could be much higher up. Makes it a lot more unwieldy to figure out where an error happens because everyone uses: > if err != nil return err
- mayoff 11mo agoSwift doesn't capture a stack trace in the `Error` object, but Xcode can break when an error is thrown if you set a “Swift Error Breakpoint”, and the debugger will show you the stack trace. Under the hood it just sets breakpoints on the runtime functions `swift_willThrow` and `swift_willThrowTypedImpl`.
- nielsbot 11mo agoThis is built in to the language. When you call code that can throw (return an error via the special return path) you either have to handle it or make the enclosing context also throwing. Assuming `canThrow()`, a function that might throw an `Error` type: func canThrow() throws { ... } Call canThrow(), don't handle errors, just rethrow them func mightThrow() throws { try canThrow() // errors thrown from here will be thrown out of `mightThrow()` ... } Alternatively, catch the errors and handle them as you wish: func mightThrow() throws { do { try canThrow() } catch { ...handle error here ...or `throw` another Error type of your choosing } ... } There are a few more ways to handle throwing calls.. For example - `try?` (ignore error result, pretend result was `nil`) - `try!` (fail fatally on error result)
- mayoff 11mo agoAnother really nice thing about Swift is that you have to put the `try` keyword in front of any expression that can throw. This means there's no hidden control flow: if some function call can throw, you're informed at the call site and don't have to look at the function declaration.
- binary132 11mo agothat sounds very similar to noexcept(bool) to me, except that noexcept can be computed, for example by deriving from some generic trait, and we presume throws unless specified non-throwing by noexcept.
- munchler 11mo ago`Result<T, E>` comes from Haskell's `Either a b` type. F# also has a `Result<'T, 'E>` type. It's funny how often functional programming languages lead the way, but imperative languages end up with the credit.
- raincole 11mo agoActually (insert nerd emoji) this is a direct descendant from tagged union type, which existed in ALGLO 68, an imperative language. Java's checked exception is just an (very anti-ergonomic) implementation of tagged union type.
- munchler 11mo agoOK, but it wasn't until functional programming came along that tagged unions were recognized as a natural way to implement sum types, which is how they are used in modern programming. The old idea of a "variant" has pretty much faded away.
- paulddraper 11mo agoAlgebraic types, immutable structures, lambdas, higher-order functions. I think FP receives a lot of "credit." Albeit the "pure" FP languages aren't popular, because 99% FP is really hard.
- JoshTriplett 11mo agoIn fairness, Rust also has unchecked exceptions (panics, if you use panic-unwind).
- majormajor 11mo agoJava too has unchecked exceptions, though the post praising "mandatory declaration" didn't mention it for either. But they CAN be caught with a standard catch which may be non-intuitive if you don't know about them, and about that, in advance.
- morshu9001 11mo agoIn high-level code, pretty much everything can fail in many different ways, and usually you're either just passing the error up or handling it in some catchall manner. Rust's behavior makes sense for its use cases, but it'd get exhausting doing this in like a web backend.
- wahern 11mo agoIt's already too exhausting to use for OOM, which is why Rust effectively punted on that from the very beginning. And the ironic thing is that anyhow::Error (or similar) seems poised to become idiomatic in Rust, which AFAIU always allocates, while the extremely belated try_ APIs... does anybody even use them? It's a shame. Were I designing a "low-level" or "systems" language, rather than put the cart before the horse and pick error results or exceptions, my litmus test would be how to make handling OOM as easy as possible. Any language construct that can make gracefully handling OOM convenient is almost by definition the fabled One True Way. And if you don't have a solution for OOM from the very beginning (whether designing a language or a project), it's just never gonna happen in a satisfactory way. Lua does it--exceptions at the language level, and either setjmp/longjmp or C++ exceptions in the VM implementation. But for a strongly typed, statically compiled language, I'm not sure which strategy (or hybrid strategy) would be best.
- morshu9001 11mo agoHm, not sure how you'd do this. OOM error auto bubbling up sounds dangerous because the inner code might not leave things in a consistent state if it exits unexpectedly, so that makes sense to manually handle, but it's tedious. Rust at least has nice ? and ! syntax for errors, unlike Go where the error-prone error handling actually ruins the entire language.
- binary132 11mo agoIf only there were some sort of automated idiom for cleaning up state under failure modes
- aw1621107 11mo ago
- barrkel 11mo agoDon't forget, failure modes pierce abstraction boundaries. An abstraction that fully specifies failure modes leaks its implementation. This is why I think checked exceptions are a dreadful idea; that, and the misguided idea that you should catch exceptions. Only code close to the exception, where it can see through the abstraction, and code far away from the exception, like a dispatch loop or request handler, where the failure mode is largely irrelevant beyond 4xx vs 5xx, should catch exceptions. Annotating all the exceptions on the call graph in between is not only pointless, it breaks encapsulation.
- binary132 11mo agoThis is a better way of expressing what I had been thinking about putting exception details behind an interface, except that in my mind encapsulating errors is just good design rather than implementation hiding, since the programmer might want to express a public error API, for example to tell the user whether a given fopen failed due to not finding the file or due to a filesystem fault.
- rerdavies 11mo agoe.what() Just include the file name in the error message. And all of this is predicated on logging errors, which is not at all user-friendly, and not remotely acceptable in GUI applications.
- binary132 11mo agoI was talking about catching different classes, not about logging. Even if you’re just implementing a string description, there’s nothing stopping you from designing that string for use in a UI element, or implementing a structured message that can specify more details usable for defining UI elements, similarly to how many REST HTTP-driven web UIs work. I don’t think I’m quite following your criticism.
- 1718627440 11mo agoIf your error codes leak the implementation details through the whole call stack you are doing it wrong. Each error code describes what fails in terms of it's function call semantics. A layer isn't supposed to just return this upwards, that wouldn't make sense, but to use it to choose it's own error return code, which is in the abstraction domain of it's function interface.
- paulddraper 11mo agoIt causes big problems in Java. For example, it plays poorly with generics. (Especially if you start doing FP, lambdas.) If Java added union types, it wouldn't be a big deal, but AFAIK this is still a limitation.