Non exhaustive list when handling errors inside a class function in Swift

Executing 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 Swift Compiler Error :

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

The following works great:

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

      

Launching Xcode 8.3 / Swift 3.1

What is the reason for the error?

+3


source to share


2 answers


As the language manual says (emphasis mine):

In sentences, you catch

do not need to handle all possible errors that the code in the sentence can cause do

. If none of the sentences catch

handles the error, the error is propagated to the surrounding area. However, the error must be done with some 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 the combined blocking block and the enclosing scope (the actual method itself) does not exhaustively handle every possible error it Piece.parse(string: string)

might throw (remember that a throwing function can throw any type of error that conforms to the protocol Error

).

You would like to add a catch all block to your chisel to deal with any other neglected 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 throw method to propagate any unhandled 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)
}

      

is compiled at the top level of the main.swift file simply because this area is special. It can catch any uncaught error, and will refer to it fatalError()

with a description of that error.

I can't find official documentation for this, but if you look in the standard library file ErrorType.swift , you 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 look at the emitted IR channel for a simplified version of the above code, it is enough to make sure that the compiler inserts the call before swift_errorInMain

when the uncaught error fails.

With playground, you get similar behavior in that the compiler makes uncaught errors at the top level - although if an error occurs, the playground seems to end silently without displaying a fatal error message.

It is difficult to investigate further due to the fact that Swift playgrounds run code in their own dedicated environment, so this means that run-time behavior can be very different from code compiled with swiftc

. Indeed, you should never use a playfield to test the actual behavior of Swift code.

+2


source


Take a look at this answer:

"Since your function can't tell what errors it is throwing (or might throw in the future), the catch blocks that catch it don't know what types of errors it might throw. Thus, in addition to handling the types of errors that you you know, you need to handle the ones that you don't work with with a generic catch operation - that way if your function changes the set of errors it throws in the future, callers will still understand their errors. "

fooobar.com/questions/51368 / ...



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

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.

+1


source







All Articles