Non Exhaustive List When Handling Errors Inside a Class Function in Swift

Non Exhaustive List When Handling Errors Inside a Class Function in Swift

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.

Swift do-try-catch syntax

There are two important points to the Swift 2 error handling model: exhaustiveness and resiliency. Together, they boil down to your do/catch statement needing to catch every possible error, not just the ones you know you can throw.

Notice that you don't declare what types of errors a function can throw, only whether it throws at all. It's a zero-one-infinity sort of problem: as someone defining a function for others (including your future self) to use, you don't want to have to make every client of your function adapt to every change in the implementation of your function, including what errors it can throw. You want code that calls your function to be resilient to such change.

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.

do {
let sandwich = try makeMeSandwich(kitchen)
print("i eat it \(sandwich)")
} catch SandwichError.NotMe {
print("Not me error")
} catch SandwichError.DoItYourself {
print("do it error")
} catch let error {
print(error.localizedDescription)
}

But let's not stop there. Think about this resilience idea some more. The way you've designed your sandwich, you have to describe errors in every place where you use them. That means that whenever you change the set of error cases, you have to change every place that uses them... not very fun.

The idea behind defining your own error types is to let you centralize things like that. You could define a description method for your errors:

extension SandwichError: CustomStringConvertible {
var description: String {
switch self {
case NotMe: return "Not me error"
case DoItYourself: return "Try sudo"
}
}
}

And then your error handling code can ask your error type to describe itself -- now every place where you handle errors can use the same code, and handle possible future error cases, too.

do {
let sandwich = try makeMeSandwich(kitchen)
print("i eat it \(sandwich)")
} catch let error as SandwichError {
print(error.description)
} catch {
print("i dunno")
}

This also paves the way for error types (or extensions on them) to support other ways of reporting errors -- for example, you could have an extension on your error type that knows how to present a UIAlertController for reporting the error to an iOS user.

Try Block throwing an error

Change to the following.. for general catch of all errors:

 if WCSession.isSupported() {
if session.watchAppInstalled {
let UserInfo = ["waste":floatWastedAmount]
do {
try session.updateApplicationContext(UserInfo)
} catch {
print("Updating the context failed")
}
}
}

Enclosing Catch is not Exhaustive (Audio Recording)

The error message is misleading. A

catch let error1 as NSError

is exhaustive, because all errors are bridged to NSError
automatically.

It seems that the compiler is confused by the forced cast

recordSettings as! [String : AnyObject]

and that causes the wrong error message. The solution is to create the
settings dictionary with the correct type in the first place:

let recordSettings: [String: AnyObject] = [
AVFormatIDKey : Int(kAudioFormatAppleLossless),
AVEncoderAudioQualityKey : AVAudioQuality.Max.rawValue,
AVEncoderBitRateKey : 320000,
AVNumberOfChannelsKey : 2,
AVSampleRateKey : 44100.0 ]

var error: NSError?
do {
soundRecorder = try AVAudioRecorder(URL: getFileURL(), settings: recordSettings)
} catch let error1 as NSError {
error = error1
soundRecorder = nil
}

Swift 2.1 - Error Handling - Multiple Catch is not compiled

You need to include a default catch block (much like when using a switch case) to make your error handling exhaustive; in case the error thrown is not one of the ones you specify.

func catchingSpecificError() {
do {
try assignName(nil) // Compiler Error displays at this line.

}catch PersonError.IsNotAPerson {
print("Propagated error is caught in catch on case .NotAPerson")
}catch {
print("default..")
}
}

Slightly off-topic, but I assume personName = name refers to a class property personName that we cannot see in your example above.


With the default catch block added, you mention in the comments below that function catchingSpecificError() does not cast the error you expect; or rather, the default catch block catches your error.

Now, since I don't know the context of your code, I cannot infer what is actually going wrong in your case. I'll post a working example for your below where---in the context of this question---the throw and catch work as expected. Note however that your use of the guard block is somewhat out of by convention. Usually you make use of guard just like if let blocks, i.e., guard let name = name else { .., which will enter the guard block if name contains nil.

Anyway, consider the following fully working example:

enum PersonError: ErrorType {
case IsNotAPerson
case IsNotAGoodPerson
case IsNotAValidPerson
}

class Person {
var personName : String? = ""

func assignName(name: String?) throws {
guard name != nil else {
throw PersonError.IsNotAPerson
}

personName = name
}

func catchingSpecificError() {
do {
try assignName(nil)

}catch PersonError.IsNotAPerson {
print("Propagated error is caught in catch on case .NotAPerson")
}catch {
print("default..")
}
}
}

var myPerson = Person()
var myName : String? = nil

myPerson.catchingSpecificError()
/* Prints: "Propagated error is caught in catch on case .NotAPerson" */

As expected, we catch PersonError.IsNotAPerson thrown by function assignName. Hopefully you can make use of this example your get your own code (the parts that you haven't shown us in your question) working.

Swift 2.0 error handling

Like the error states, you need to add a final catch without constraints.

Create a catch/replace error counterpart that does not complete the publisher?

An error completing the pipeline is part of the contract by which publishers and subscribers work. And this makes sense. If an upstream publisher throws an error and doesn't know how to recover on its own, it's basically done. It's not unlike a function throwing an error.

In your example, tryMap throws an error, and although a downstream like replaceError can transform the error for its downstream, it does not expect any more values from its upstream, so it too completes.

flatMap shields the rest of the pipeline from the error-throwing publisher. In other words, the upstream of flatMap doesn't throw an error, and flatMap itself doesn't throw an error since the returned inner publisher (the pipeline with Just/tryMap/catch) handles the error.

Just([1, 2, 3, 5, 6])
.flatMap {
Just($0)
.tryMap { _ in throw DummyError() }
.catch { _ in Just(4) }
}
.sink { print($0) }

Single try meaning in Swift

In addition to the cases you mentioned, you can call try at
top-level code. Here is a simple self-contained example:

// main.swift:
enum MyError : Error {
case failed
}

func foo() throws -> Int {
throw MyError.failed
}

defer { print("Good bye.") }

let x = try foo()
print(x)

You can compile and run this as a Xcode "Command Line Project"
or directly from the command line:


$ swiftc main.swift

$ ./main
Good bye.
Fatal error: Error raised at top level: main.MyError.failed: file /BuildRoot/Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-900.0.74.1/src/swift/stdlib/public/core/ErrorType.swift, line 187
Illegal instruction: 4

The failed try in the top-level code causes the program to
terminate with an error message. Deferred statement (if present) will be executed however.

This is slightly different from using a forced try! statement,
which causes the program to abort as well, but immediately, without executing deferred statements. (This can be relevant if deferred
statements are used to clean-up resources, e.g. remove temporary files).


The error message originates from ErrorType.swift, line 187:

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

(also observed in Non Exhaustive List When Handling Errors Inside a Class Function in Swift).

Apparently the top-level code behaves as if embedded in a
do-catch block:

do {
func foo() throws -> Int {
throw NSError(domain: "foo", code: 0, userInfo: nil)
}

defer { print("Good bye.") }

let x = try foo()
} catch {
fatalError("Error raised at top level: \(String(reflecting: error))")
}


Related Topics



Leave a reply



Submit