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.