4

Running the following snippet in a playground / project:

class Piece {
    enum ParseError: Error {
        case unknown(string: String)
    }

    class func parse(string: String) throws {
        throw ParseError.unknown(string: string)
    }
}

class Board {
    class func parse(string: String) {
        do {
            try Piece.parse(string: string)
        } catch Piece.ParseError.unknown(let string) {
            print(string)
        }
    }
}

Gives a Swift Compiler Error:

Errors thrown from here are not handled because the enclosing catch is not exhaustive

The following works fine:

enum ParseError: Error {
    case unknown(string: String)
}

func parse(string: String) throws {
    throw ParseError.unknown(string: string)
}

do {
    try parse(string: "rook")
} catch ParseError.unknown(let string) {
    print(string)
}

Running Xcode 8.3 / Swift 3.1

What's the reason for the error?

Kevin Sylvestre
  • 37,288
  • 33
  • 152
  • 232

2 Answers2

4

As said by the language guide (emphasis mine):

The catch clauses don’t have to handle every possible error that the code in its do clause can throw. If none of the catch clauses handle the error, the error propagates to the surrounding scope. However, the error must be handled by some surrounding scope [...]

Therefore, your example

class Board {
    class func parse(string: String) {
        do {
            try Piece.parse(string: string)
        } catch Piece.ParseError.unknown(let string) {
            print(string)
        }
    }
}

is illegal – because combined, the do-catch block and the enclosing scope (the actual method itself) don't exhaustively handle every possible error that Piece.parse(string: string) can throw (remember that a throwing function can throw any error type that conforms to the Error protocol).

You would either want to add a "catch all" block to your do-catch in order to handle any other thrown error:

do {
    try Piece.parse(string: string)
} catch Piece.ParseError.unknown(let string) {
    print(string)
} catch {
    // do error handling for any miscellaneous errors here.
    print(error)
}

Or make parse(string:) a throwing method in order to propagate any uncaught errors back to the caller.

class func parse(string: String) throws {
    // ...
}

The only reason why

enum ParseError: Error {
    case unknown(string: String)
}

func parse(string: String) throws {
    throw ParseError.unknown(string: string)
}

do {
    try parse(string: "rook")
} catch ParseError.unknown(let string) {
    print(string)
}

compiles at the top-level of a main.swift file is simply because that scope is special. It can catch any uncaught error, and upon doing so will invoke fatalError() with a description of that error.

I cannot find any official documentation for this, but if you look in the Standard Library's ErrorType.swift file, you'll see the following function:

/// Invoked by the compiler when code at top level throws an uncaught error.
@_silgen_name("swift_errorInMain")
public func _errorInMain(_ error: Error) {
  fatalError("Error raised at top level: \(String(reflecting: error))")
}

And if we examine the IR emitted for a simplified version of the above code, sure enough we can see that the compiler inserts a call to swift_errorInMain upon an uncaught error being thrown.

With a playground, you get a similar behaviour in that the compiler allows uncaught errors at the top-level – although in the case of an error being thrown, the playground just seems to silently terminate without displaying a fatal error message.

It's difficult to investigate further due to the fact that Swift playgrounds run code in their own special environment, therefore meaning that the runtime behaviour can wildly differ from code compiled with swiftc. Really, you should never use a playground to test the actual behaviour of Swift code.

Hamish
  • 78,605
  • 19
  • 187
  • 280
2

Take a look to this answer:

"Because your function can't say what kind of errors it throws (or might throw in the future), the catch blocks that catch it errors don't know what types of errors it might throw. So, in addition to handling the error types you know about, you need to handle the ones you don't with a universal catch statement -- that way if your function changes the set of errors it throws in the future, callers will still catch its errors."

https://stackoverflow.com/a/30720807/6203030

In your case, this can be solved by adding this:

class Board {
    class func parse(string: String) {
        do {
            try Piece.parse(string: string)
        } catch Piece.ParseError.unknown(let string) {
            print(string)
        } catch let error {
            print(error.localizedDescription)
        }
    }
}

You need to catch all errors.

Community
  • 1
  • 1
Aitor Pagán
  • 423
  • 7
  • 18