swift force-unwrapping exception not propagated
Asked Answered
A

3

20

I've run into this silly behaviour in swift where force-unwrapping an optional does not propagate.

From the documentation:

Trying to use ! to access a non-existent optional value triggers a runtime error. Always make sure that an optional contains a non-nil value before using ! to force-unwrap its value.

To reproduce:

func foo(bar:String?) throws{
    print(bar!);
}

And

try foo(nil);

This does not seem logical or consistent to me and I can't find any documentation on this subject.

Is this by design?

Arillode answered 6/1, 2016 at 8:54 Comment(4)
I'm not sure that throwing an error with throw (which is what causes errors to be propagated from within such a function) is the same thing that happens when you force-unwrap a nil. I think I read somewhere that it is implemented as an assert().Shrewd
That would explain it, but thats terrible. Maybe there is an argument for it but in a language that supports exceptions it just seems inconsistent.Arillode
@Greg: Nicolas is right. Note that try/catch handles Swift errors (values conforming to ErrorType which are thrown). That is completely unrelated to runtime errors or exceptions. (The documentation does not even mention the word "exception" in connection with throw/try/catch, only "Error handling".)Dunsinane
I guess it's easy to confuse swift errors with exceptions. Most other programming languages use similar keywords (try/catch) to deal with exception handling.Shrewd
D
20

From the documentation:

Error Handling

Error handling is the process of responding to and recovering from error conditions in your program. Swift provides first-class support for throwing, catching, propagating, and manipulating recoverable errors at runtime.

...

Representing and Throwing Errors

In Swift, errors are represented by values of types that conform to the ErrorType protocol. This empty protocol indicates that a type can be used for error handling.

(Note: ErrorType has been renamed to Error in Swift 3)

So with try/catch you handle Swift errors (values of types that conform to the ErrorType protocol) which are thrown. This is completely unrelated to runtime errors and runtime exceptions (and also unrelated to NSException from the Foundation library).

Note that the Swift documentation on error handling does not even use the word "exception", with the only exception (!) in (emphasis mine) in:

NOTE

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.

The unwrapping of optionals which are nil does not throw a Swift error (which could be propagated) and cannot be handled with try.

You have to use the well-known techniques like optional binding, optional chaining, checking against nil etc.

Dunsinane answered 6/1, 2016 at 9:16 Comment(2)
I've never tried to use try to catch unwrapping, I only talk about them propagating. But you answer that just fine. One usecase that I ran into this is using SwiftyJSON and parsing json. I have an Entity that contains a child Entity that would simply throw (propagate) the non-existent field in the json response. I now use optional initializers to achive the same thing, but now there are 3 more lines of code :PArillode
@Greg: Well, any thrown error must be catched somewhere. But I have changed the wording to match your question better.Dunsinane
K
5

this 'self explanatory' example can help you to see the difference between raising an runtime exception and throwing an error E conforming to ErrorType protocol.

struct E: ErrorType{}
func foo(bar:String?) throws {
    if let error = bar where error == "error" {
            throw E()
    }
    print(bar, "is valid parameter, but don't try to access bar.characters, it crash your code! (if bar == nil)")
    // here is everything OK 
    let bar = bar!
    // but here it crash!!
    _ = bar.characters
}

do {
    try foo("error")
    // next line is not accessible here ...
    try foo(nil)
} catch {
    print("\"error\" as parameter of foo() throws an ERROR!")
}
do {
    try foo(nil) // fatal error: unexpectedly found nil while unwrapping an Optional value
} catch {

}

it prints

"error" as parameter of foo() throws an ERROR!
nil is valid parameter, but don't try to access bar.characters, it crash your code! (if bar == nil)
fatal error: unexpectedly found nil while unwrapping an Optional value

raising an runtime exception is fatal error in your code.

Kadner answered 6/1, 2016 at 11:55 Comment(0)
C
0

the force-unwrapping is implemented exactly as the documentation described! But you may be looking for something like this:

try myOptional.unwrapped() // Throws an error if it is `nil`

This trowing unwrapped function can be implemented simply like:

extension Optional {
    enum Error: Swift.Error {
        case unexpectedNil
    }

    func unwrapped() throws -> Wrapped {
        if let self { return self }
        else { throw Error.unexpectedNil }
    }
}

So now you can catch Optional.Error.unexpectedNil

Caustic answered 3/3 at 12:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.