Preferences

Swift 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/ErrorHandl...

I 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?
> 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)

Yes 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.

> And so the caller has to be prepared for everything: a later version of the library might suddenly return a new type of exception.

But you get the same with checked exceptions in Java. Yes, an interface will say foo can only throw FooException, but if you want to do anything when you get a FooException, you have to look inside to figure out what exactly was wrong, and what’s inside that FooException isn’t limited.

A later version of the library may suddenly throw a FooException with a BarException inside it.

What 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/s...

When using a language forcing checked exceptions, you would know, wouldn't you?
Swift gained limited support for “typed throws” in Swift 6.0 (2024).

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.

Typed 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

I 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.

Another 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.
From 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

This 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)

Swift 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`.
that 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.

This item has no comments currently.

Keyboard Shortcuts

Story Lists

j
Next story
k
Previous story
Shift+j
Last story
Shift+k
First story
o Enter
Go to story URL
c
Go to comments
u
Go to author

Navigation

Shift+t
Go to top stories
Shift+n
Go to new stories
Shift+b
Go to best stories
Shift+a
Go to Ask HN
Shift+s
Go to Show HN

Miscellaneous

?
Show this modal