Skip to content

Support Error Handling in Swift 2.0 + Update README #220

Closed

Description

@Jeehut

I just read this in the README:

// Note: Swift currently doesn't have exceptions.
//       Only Objective-C code can raise exceptions
//       that Nimble will catch.

This is not true any more. Swift 2.0 introduced Exceptions and Error handling in general – alongside guard and defer statements to make this easier. There are three things to clear out as far as I can see:

  • Check if Code needs update (I'm guessing 'yes' cause Nimble seems to be catching exceptions)
  • Update Code to support Swift native Error Handling
  • Update README to reflect the changes (if any)

Activity

  1. jeffh commented on Dec 8, 2015

    @jeffh
    Member

    Thanks for filing an issue @dschee.

    It definitely needs to be more clarification. But Swift's error handling is separate from Objective-C's exception handling.

    // swift does not catch this exception
    do {
        NSException(name: "hello", reason: "world", userInfo: nil).raise()
    } catch error {
        // ...
    }

    While it's good practice to throw exceptions to indicate programmer error, various systems do throw exceptions (eg - CoreData). Also testing throwing of exceptions is useful to test in a library.

    Nimble also does handle unexpected swift errors. Also there's a throwError matcher if you expect an error to be thrown.

  2. Jeehut commented on Jan 17, 2016

    @Jeehut
    ContributorAuthor

    Thanks for the clarification @jeffh, but I'm not sure I understood all you wanted to say correctly. Does the throwError matcher also match against Swift exceptions? Or is what you want to say that Swift still doesn't have exceptions but instead "throws errors"?

    In any case, I didn't see the throwError method before, thanks for that, I think that is what I was asking for. But I still think the note from the README I quoted is clear. At least when I read it I didn't even look for another way of catching errors thrown by the Swift native way cause I thought Nimble is outdated.

    Maybe we should change it to something like:

    Note: Swifts exception handling works different than Objective-C's. It doesn't catch exceptions raised by NSException. Use the matcher throwError instead if you want to test against exceptions thrown the Swift way.

    Or do I still misunderstand?

  3. jeffh commented on Jan 18, 2016

    @jeffh
    Member

    Sadly, there's limited vocabulary for these definitions. Yes, Swift doesn't have exceptions, but it has syntactic sugar around errors. Hence the differing names of throwError and raiseException.

    Exceptions are generally associated to its common implementations:

    • Exceptions need to find their handlers when thrown (aka - find the closest catch / finally block).
      • This implies an extra runtime cost when exceptions are thrown
    • Exceptions use extra storage for handlers + stack metadata
      • Which means having a CPU cache miss when exceptions occur because it's stored in distant memory addresses (0-cost exceptions implementation)
      • Or always incur a small overhead for being in a try / catch for cleaning local memory addresses, if that's used (naive exception implementations)
    • Many implementations capture stack traces when thrown
      • Capture stack information requires more runtime calls and address to function lookups

    All of which make exceptions are slower than return values. That's why I'm afraid of using the term exception for Swift's error handling model. It's technically incorrect, even though it might be more common to say it's an exception.

    In comparison, errors are more accepted as a data structure that is returned by a function. A perfect example is to see how the go programming language handles errors:

    // go code
    func stuff() error {
        f, err := os.Open("filename.ext") // Open can fail
        if err != nil {
            // handle error: 98% time we return the error to our caller
            return err
        }
        // do more stuff
    }

    Obviously this is annoying to write an if case for every error. Especially since the common case is for a function to return its error back to the caller. So swift has syntax that converts code to what go programmers would normally write in functions:

    // swift code
    func stuff() throws {
        let f = try open("filename.ext")
        // do more stuff
    }

    Which is much more concise. When handling errors, swift uses syntax similar to how developers are familiar with exceptions:

    do {
        try stuff()
    } catch let error {
        // log error
    }

    But that can be a simple conversion to an if statement in compiled code (like go's):

    let error = try stuff() // fake: pretending `try` returns the error
    if error != nil {
        // log error
    }

    Of course, it's useful to quote a more authoritative source about swift's implementation. A note under the "Handling Errors" section of the Swift Language Guide that says:

    Error handling in Swift resembles exception handling in other languages, with the use of the try, catch and throw keywords. Unlike exception handling in many languages—including Objective-C—error handling in Swift does not involve unwinding the call stack, a process that can be computationally expensive. As such, the performance characteristics of a throw statement are comparable to those of a return statement.

    This is the reason why Swift always refers them to errors, and not exceptions.


    All that as a long-winded way to say:

    • Objective-C has errors (NSError) and exceptions (NSException)
    • Swift only has errors (ErrorType)

    And with the above explanation, you can say:

    • Errors don't suffer the performance cost of exceptions
    • Swift makes errors look like exceptions

    I hope that was a bit clearer 😉.

  4. Jeehut commented on Jan 18, 2016

    @Jeehut
    ContributorAuthor

    @jeffh Thank you very much for that very detailed explanation. I realize now that I had fallen into the trap to assume I'm dealing with real exception handling when using do, try and catch – I simply had never heard of "catching an error" before, so I assumed the catch must be there for exceptions. Sorry for that misconception on my side. From my perspective, this issue can be closed now.

    But in case you care about people who could fall into the same trap as I did, you might want to alter the README to state something like this (providing your great explanation above):

    Note: Swift currently doesn't have exceptions (see also #220). Only Objective-C code can raise exceptions that Nimble will catch.

  5. added a commit that references this issue on Jan 19, 2016
  6. jeffh commented on Jan 19, 2016

    @jeffh
    Member

    Done, thanks for the feedback @dschee. 👍

  7. added a commit that references this issue on Jul 31, 2019
  8. added a commit that references this issue on May 3, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions