[Swift] Perform error handling with do-catch statement (Part 2)

This article is a continuation of [Swift] Do-catch statement to handle errors (Part 1).

In the previous article, I explained the basic implementation method of the do-catch statement. Described about throws keyword and rethrows keyword. If you haven't heard these keywords at all, please take a look!

Let's continue.

try keyword

The try keyword is added when performing processing that may cause a ** error. ** ** That is, when calling the process with the throws keyword.

Add the try keyword before calling those operations, Write as try function name (argument).

In the following sample code If a value less than half the Max value of the Int type is passed to the argument, double that value and return it. If the value is more than half of the Max value, error processing is performed.

Isn't there a return in the guard statement! You might think, ** You don't necessarily need a return, you can throw it! ** **

Is it an image that returns an error instead of returning a value?

This time, we are passing a pattern that passes 100 and a Max value of Int type. Of course, the latter is caught by the guard statement, so it will be an error handling.


enum SomeError: Error {
    case overInt
}

func double(value: Int) throws -> Int {
    guard value <= Int.max / 2 else {
        throw SomeError.overInt
    }
    
    return value * 2
}

do {
    let value1 = try double(value: 100)
    print(value1)
    let value2 = try double(value: Int.max)
    print(value2)
} catch {
    print("Error: \(error)")
}

Execution result
200
Error: overInt

try! keyword

It came out ! "!" Is used so much that you might think it is .

! Other than try! The usage of the keyword is Is the Optional \ type forced unwrapping famous?

The try! keyword is also a fairly bullish type (laughs)

I wrote earlier that ** the process with the throws keyword adds the try keyword **.

However, I knew that in certain situations the error would never occur, In some cases, you don't want to bother to describe error handling.

In that case, you can use try! The keyword . ** You can ignore the error by using try! Instead of try. ** **

Also, the try! Keyword ignores the error, so It can be used even if it is not inside the do clause or inside the process for which the throws keyword is specified.

I changed some of the contents of the sample code. value1 is working, but value2 is getting a runtime error.


enum SomeError: Error {
    case overInt
}

func double(value: Int) throws -> Int {
    guard value <= Int.max / 2 else {
        throw SomeError.overInt
    }
    
    return value * 2
}


let value1 = try! double(value: 100)
print(value1)
let value2 = try! double(value: Int.max)   //Run-time error
print(value2)

Execution result
200
Fatal error: 'try!' expression unexpectedly raised an error: __lldb_expr_297.SomeError.overInt: file __lldb_expr_297/MyPlayground.playground, line 17

Does the try! keyword undermine the safety of Swift's pride? I thought, but when I think about it, ** I find it too tedious to deal with all error cases in any case. ** ** It's also impractical and less readable.

However, considering that the error can be ignored implicitly, ** I don't know where the error was ignored, The result is an unreliable program. ** **

However, the part with "!" Is implicitly the part that ignores the error, so ** As a result, the try! Keyword balances practicality and safety. ** **

try? Keyword

Even if the throws keyword is specified ** You may not need the details of the error when using it. ** ** The try? keyword is used in such cases.

Use the ** try! Keyword when an error never occurs. The try? Keyword is an image to use when an error occurs but details are not needed. ** **

You can omit the do-catch statement by calling the process with the ** try? keyword. ** ** But instead, the return value of the function is ** Optional \ type **.

In other words, it is similar to error handling by the Optional \ type.

The difference from the try keyword is It can be used even if it is not inside the do clause or throws keyword of the do-catch statement.

In the following sample code It defines a function that doubles the argument value and returns it.

if let value = try? Double (value: Int.max) {} is The guard statement gets caught and throw SomeError.overInt is executed, but The value that actually goes into value is nil.


enum SomeError: Error {
    case overInt
}

func double(value: Int) throws -> Int {
    guard value <= Int.max / 2 else {
        throw SomeError.overInt
    }
    
    return value * 2
}

// ①
if let value = try? double(value: 2) {
    print(value)
} else {
    print("error")
}

// ②
if let value = try? double(value: Int.max) {
    print(value)
} else {
    print("error")
}

Execution result
4
error

Also, the try? Keyword can be used for the initializer, so You can also treat it like a failable initializer.


enum SampleEnum: Error {
    case outOfRange
}

struct SampleStruct {
    var value: Int
    
    init(value: Int) throws {
        guard case 10...19 = value else {
            throw SampleEnum.outOfRange
        }
        self.value = value
    }
}

if let value = try? SampleStruct(value: 13) {
    print(value)
} else {
    print("error")
}

if let value = try? SampleStruct(value: 20) {
    print(value)
} else {
    print("error")
}

Execution result
SampleStruct(value: 13)
error

Processing regardless of the presence or absence of an error due to the defer statement

You can use the defer statement to do something with or without an error. The description method is very simple, just add it where you want to execute it.

First, here is the process when the defer statement is not used.

Since an error always occurs in try sampleFunc (), The hello () function is never executed.


enum SampleError: Error {
    case someError
}

func sampleFunc() throws {
    print("sampleFunc()Run")
    throw SampleError.someError
}

func hello() {
    print("Hello")
}

do {
    try sampleFunc()
    hello()
} catch {
    print("Error: \(error)")
}

Execution result
sampleFunc()Run
Error: someError

The following is the processing when the defer statement is used.


enum SampleError: Error {
    case someError
}

func sampleFunc() throws {
    print("sampleFunc()Run")
    throw SampleError.someError
}

func hello() {
    print("Hello")
}

do {
    defer {
        hello()
    }
    try sampleFunc()
} catch {
    print("Error: \(error)")
}

Execution result
sampleFunc()Run
Hello
Error: someError

Where the defer statement can be used It is executed after the do clause is over.

It seems to be useful when there is a process you want to do at the end!

This is the end of the explanation related to the try keyword. Do-catch is difficult, but I definitely want to be able to use it ...

The continuation explains ** when to use the try keyword **, so please have a look. -> [Swift] Perform error handling with do-catch statement (Part 3)

Also, please have a look at the article about other error handling. -[Swift] Perform error handling with Optional \ type -[Swift] Perform error handling with Result \ <Success, Failure> type -[Swift] Termination of program by fatalError function[Swift] End of program by assertion

Thank you for watching until the end.

Recommended Posts

[Swift] Perform error handling with do-catch statement (Part 2)
[Swift] Perform error handling with do-catch statement (Part 1)
[Swift] Perform error handling with Optional <Wrapped> type
[Swift] Error handling
[Swift] Perform error processing with Result <Success, Failure> type
error code 400 appears when deploying with release: perform
Customize REST API error response with Spring Boot (Part 2)
Customize REST API error response with Spring Boot (Part 1)
[Swift] Perform error handling with do-catch statement (Part 2)
[Swift] Perform error handling with do-catch statement (Part 1)
[Swift] Perform error handling with Optional <Wrapped> type
[Swift] Error handling
[Swift] Perform error processing with Result <Success, Failure> type
error code 400 appears when deploying with release: perform
Customize REST API error response with Spring Boot (Part 2)
Customize REST API error response with Spring Boot (Part 1)