Initial commit
This commit is contained in:
993
skills/programming-swift/LanguageGuide/ErrorHandling.md
Normal file
993
skills/programming-swift/LanguageGuide/ErrorHandling.md
Normal file
@@ -0,0 +1,993 @@
|
||||
# Error Handling
|
||||
|
||||
Respond to and recover from errors.
|
||||
|
||||
*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.
|
||||
|
||||
Some operations
|
||||
aren't guaranteed to always complete execution or produce a useful output.
|
||||
Optionals are used to represent the absence of a value,
|
||||
but when an operation fails,
|
||||
it's often useful to understand what caused the failure,
|
||||
so that your code can respond accordingly.
|
||||
|
||||
As an example, consider the task of reading and processing data from a file on disk.
|
||||
There are a number of ways this task can fail, including
|
||||
the file not existing at the specified path,
|
||||
the file not having read permissions, or
|
||||
the file not being encoded in a compatible format.
|
||||
Distinguishing among these different situations
|
||||
allows a program to resolve some errors
|
||||
and to communicate to the user any errors it can't resolve.
|
||||
|
||||
> Note: Error handling in Swift interoperates with error handling patterns
|
||||
> that use the `NSError` class in Cocoa and Objective-C.
|
||||
> For more information about this class,
|
||||
> see [Handling Cocoa Errors in Swift](https://developer.apple.com/documentation/swift/cocoa_design_patterns/handling_cocoa_errors_in_swift).
|
||||
|
||||
## Representing and Throwing Errors
|
||||
|
||||
In Swift, errors are represented by
|
||||
values of types that conform to the `Error` protocol.
|
||||
This empty protocol indicates that a type
|
||||
can be used for error handling.
|
||||
|
||||
Swift enumerations are particularly well suited to modeling
|
||||
a group of related error conditions,
|
||||
with associated values allowing for additional information
|
||||
about the nature of an error to be communicated.
|
||||
For example, here's how you might represent the error conditions
|
||||
of operating a vending machine inside a game:
|
||||
|
||||
```swift
|
||||
enum VendingMachineError: Error {
|
||||
case invalidSelection
|
||||
case insufficientFunds(coinsNeeded: Int)
|
||||
case outOfStock
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `throw-enum-error`
|
||||
|
||||
```swifttest
|
||||
-> enum VendingMachineError: Error {
|
||||
case invalidSelection
|
||||
case insufficientFunds(coinsNeeded: Int)
|
||||
case outOfStock
|
||||
}
|
||||
```
|
||||
-->
|
||||
|
||||
Throwing an error lets you indicate that something unexpected happened
|
||||
and the normal flow of execution can't continue.
|
||||
You use a `throw` statement to throw an error.
|
||||
For example,
|
||||
the following code throws an error to indicate
|
||||
that five additional coins are needed by the vending machine:
|
||||
|
||||
```swift
|
||||
throw VendingMachineError.insufficientFunds(coinsNeeded: 5)
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `throw-enum-error`
|
||||
|
||||
```swifttest
|
||||
-> throw VendingMachineError.insufficientFunds(coinsNeeded: 5)
|
||||
xx fatal error
|
||||
```
|
||||
-->
|
||||
|
||||
## Handling Errors
|
||||
|
||||
When an error is thrown,
|
||||
some surrounding piece of code must be responsible
|
||||
for handling the error ---
|
||||
for example, by correcting the problem,
|
||||
trying an alternative approach,
|
||||
or informing the user of the failure.
|
||||
|
||||
There are four ways to handle errors in Swift.
|
||||
You can propagate the error from a function to the code that calls that function,
|
||||
handle the error using a `do`-`catch` statement,
|
||||
handle the error as an optional value,
|
||||
or assert that the error will not occur.
|
||||
Each approach is described in a section below.
|
||||
|
||||
When a function throws an error,
|
||||
it changes the flow of your program,
|
||||
so it's important that you can quickly identify places in your code that can throw errors.
|
||||
To identify these places in your code, write the `try` keyword ---
|
||||
or the `try?` or `try!` variation ---
|
||||
before a piece of code that calls a function, method, or initializer that can throw an error.
|
||||
These keywords are described in the sections below.
|
||||
|
||||
> 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 doesn't 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.
|
||||
|
||||
### Propagating Errors Using Throwing Functions
|
||||
|
||||
To indicate that a function, method, or initializer can throw an error,
|
||||
you write the `throws` keyword in the function's declaration
|
||||
after its parameters.
|
||||
A function marked with `throws` is called a *throwing function*.
|
||||
If the function specifies a return type,
|
||||
you write the `throws` keyword before the return arrow (`->`).
|
||||
|
||||
<!--
|
||||
TODO Add discussion of throwing initializers
|
||||
-->
|
||||
|
||||
```swift
|
||||
func canThrowErrors() throws -> String
|
||||
|
||||
func cannotThrowErrors() -> String
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `throwingFunctionDeclaration`
|
||||
|
||||
```swifttest
|
||||
-> func canThrowErrors() throws -> String
|
||||
>> { return "foo" }
|
||||
|
||||
-> func cannotThrowErrors() -> String
|
||||
>> { return "foo" }
|
||||
```
|
||||
-->
|
||||
|
||||
<!--
|
||||
- test: `throwing-function-cant-overload-nonthrowing`
|
||||
|
||||
```swifttest
|
||||
-> func f() -> Int { return 10 }
|
||||
-> func f() throws -> Int { return 10 } // Error
|
||||
!$ error: invalid redeclaration of 'f()'
|
||||
!! func f() throws -> Int { return 10 } // Error
|
||||
!! ^
|
||||
!$ note: 'f()' previously declared here
|
||||
!! func f() -> Int { return 10 }
|
||||
!! ^
|
||||
```
|
||||
-->
|
||||
|
||||
<!--
|
||||
- test: `throwing-parameter-can-overload-nonthrowing`
|
||||
|
||||
```swifttest
|
||||
-> func f(callback: () -> Int) {}
|
||||
-> func f(callback: () throws -> Int) {} // Allowed
|
||||
```
|
||||
-->
|
||||
|
||||
<!--
|
||||
TODO: Add more assertions to test these behaviors
|
||||
-->
|
||||
|
||||
<!--
|
||||
TODO: Write about the fact the above rules that govern overloading
|
||||
for throwing and nonthrowing functions.
|
||||
-->
|
||||
|
||||
A throwing function propagates errors that are thrown inside of it
|
||||
to the scope from which it's called.
|
||||
|
||||
> Note: Only throwing functions can propagate errors.
|
||||
> Any errors thrown inside a nonthrowing function
|
||||
> must be handled inside the function.
|
||||
|
||||
In the example below,
|
||||
the `VendingMachine` class has a `vend(itemNamed:)` method
|
||||
that throws an appropriate `VendingMachineError`
|
||||
if the requested item isn't available,
|
||||
is out of stock,
|
||||
or has a cost that exceeds the current deposited amount:
|
||||
|
||||
```swift
|
||||
struct Item {
|
||||
var price: Int
|
||||
var count: Int
|
||||
}
|
||||
|
||||
class VendingMachine {
|
||||
var inventory = [
|
||||
"Candy Bar": Item(price: 12, count: 7),
|
||||
"Chips": Item(price: 10, count: 4),
|
||||
"Pretzels": Item(price: 7, count: 11)
|
||||
]
|
||||
var coinsDeposited = 0
|
||||
|
||||
func vend(itemNamed name: String) throws {
|
||||
guard let item = inventory[name] else {
|
||||
throw VendingMachineError.invalidSelection
|
||||
}
|
||||
|
||||
guard item.count > 0 else {
|
||||
throw VendingMachineError.outOfStock
|
||||
}
|
||||
|
||||
guard item.price <= coinsDeposited else {
|
||||
throw VendingMachineError.insufficientFunds(coinsNeeded: item.price - coinsDeposited)
|
||||
}
|
||||
|
||||
coinsDeposited -= item.price
|
||||
|
||||
var newItem = item
|
||||
newItem.count -= 1
|
||||
inventory[name] = newItem
|
||||
|
||||
print("Dispensing \(name)")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `errorHandling`
|
||||
|
||||
```swifttest
|
||||
>> enum VendingMachineError: Error {
|
||||
>> case invalidSelection
|
||||
>> case insufficientFunds(coinsNeeded: Int)
|
||||
>> case outOfStock
|
||||
>> }
|
||||
-> struct Item {
|
||||
var price: Int
|
||||
var count: Int
|
||||
}
|
||||
|
||||
-> class VendingMachine {
|
||||
-> var inventory = [
|
||||
"Candy Bar": Item(price: 12, count: 7),
|
||||
"Chips": Item(price: 10, count: 4),
|
||||
"Pretzels": Item(price: 7, count: 11)
|
||||
]
|
||||
-> var coinsDeposited = 0
|
||||
|
||||
-> func vend(itemNamed name: String) throws {
|
||||
guard let item = inventory[name] else {
|
||||
throw VendingMachineError.invalidSelection
|
||||
}
|
||||
|
||||
guard item.count > 0 else {
|
||||
throw VendingMachineError.outOfStock
|
||||
}
|
||||
|
||||
guard item.price <= coinsDeposited else {
|
||||
throw VendingMachineError.insufficientFunds(coinsNeeded: item.price - coinsDeposited)
|
||||
}
|
||||
|
||||
coinsDeposited -= item.price
|
||||
|
||||
var newItem = item
|
||||
newItem.count -= 1
|
||||
inventory[name] = newItem
|
||||
|
||||
print("Dispensing \(name)")
|
||||
}
|
||||
}
|
||||
```
|
||||
-->
|
||||
|
||||
The implementation of the `vend(itemNamed:)` method
|
||||
uses `guard` statements to exit the method early and throw appropriate errors
|
||||
if any of the requirements for purchasing a snack aren't met.
|
||||
Because a `throw` statement immediately transfers program control,
|
||||
an item will be vended only if all of these requirements are met.
|
||||
|
||||
Because the `vend(itemNamed:)` method propagates any errors it throws,
|
||||
any code that calls this method must either handle the errors ---
|
||||
using a `do`-`catch` statement, `try?`, or `try!` ---
|
||||
or continue to propagate them.
|
||||
For example,
|
||||
the `buyFavoriteSnack(person:vendingMachine:)` in the example below
|
||||
is also a throwing function,
|
||||
and any errors that the `vend(itemNamed:)` method throws will
|
||||
propagate up to the point where the `buyFavoriteSnack(person:vendingMachine:)` function is called.
|
||||
|
||||
```swift
|
||||
let favoriteSnacks = [
|
||||
"Alice": "Chips",
|
||||
"Bob": "Licorice",
|
||||
"Eve": "Pretzels",
|
||||
]
|
||||
func buyFavoriteSnack(person: String, vendingMachine: VendingMachine) throws {
|
||||
let snackName = favoriteSnacks[person] ?? "Candy Bar"
|
||||
try vendingMachine.vend(itemNamed: snackName)
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `errorHandling`
|
||||
|
||||
```swifttest
|
||||
-> let favoriteSnacks = [
|
||||
"Alice": "Chips",
|
||||
"Bob": "Licorice",
|
||||
"Eve": "Pretzels",
|
||||
]
|
||||
-> func buyFavoriteSnack(person: String, vendingMachine: VendingMachine) throws {
|
||||
let snackName = favoriteSnacks[person] ?? "Candy Bar"
|
||||
try vendingMachine.vend(itemNamed: snackName)
|
||||
}
|
||||
>> var v = VendingMachine()
|
||||
>> v.coinsDeposited = 100
|
||||
>> try buyFavoriteSnack(person: "Alice", vendingMachine: v)
|
||||
<< Dispensing Chips
|
||||
```
|
||||
-->
|
||||
|
||||
In this example,
|
||||
the `buyFavoriteSnack(person: vendingMachine:)` function looks up a given person's favorite snack
|
||||
and tries to buy it for them by calling the `vend(itemNamed:)` method.
|
||||
Because the `vend(itemNamed:)` method can throw an error,
|
||||
it's called with the `try` keyword in front of it.
|
||||
|
||||
Throwing initializers can propagate errors in the same way as throwing functions.
|
||||
For example,
|
||||
the initializer for the `PurchasedSnack` structure in the listing below
|
||||
calls a throwing function as part of the initialization process,
|
||||
and it handles any errors that it encounters by propagating them to its caller.
|
||||
|
||||
```swift
|
||||
struct PurchasedSnack {
|
||||
let name: String
|
||||
init(name: String, vendingMachine: VendingMachine) throws {
|
||||
try vendingMachine.vend(itemNamed: name)
|
||||
self.name = name
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `errorHandling`
|
||||
|
||||
```swifttest
|
||||
-> struct PurchasedSnack {
|
||||
let name: String
|
||||
init(name: String, vendingMachine: VendingMachine) throws {
|
||||
try vendingMachine.vend(itemNamed: name)
|
||||
self.name = name
|
||||
}
|
||||
}
|
||||
>> do {
|
||||
>> let succeeds = try PurchasedSnack(name: "Candy Bar", vendingMachine: v)
|
||||
>> print(succeeds)
|
||||
>> } catch {
|
||||
>> print("Threw unexpected error.")
|
||||
>> }
|
||||
<< Dispensing Candy Bar
|
||||
<< PurchasedSnack(name: "Candy Bar")
|
||||
>> do {
|
||||
>> let throwsError = try PurchasedSnack(name: "Jelly Baby", vendingMachine: v)
|
||||
>> print(throwsError)
|
||||
>> } catch {
|
||||
>> print("Threw EXPECTED error.")
|
||||
>> }
|
||||
<< Threw EXPECTED error.
|
||||
```
|
||||
-->
|
||||
|
||||
### Handling Errors Using Do-Catch
|
||||
|
||||
You use a `do`-`catch` statement to handle errors
|
||||
by running a block of code.
|
||||
If an error is thrown by the code in the `do` clause,
|
||||
it's matched against the `catch` clauses
|
||||
to determine which one of them can handle the error.
|
||||
|
||||
Here is the general form of a `do`-`catch` statement:
|
||||
|
||||
```swift
|
||||
do {
|
||||
try <#expression#>
|
||||
<#statements#>
|
||||
} catch <#pattern 1#> {
|
||||
<#statements#>
|
||||
} catch <#pattern 2#> where <#condition#> {
|
||||
<#statements#>
|
||||
} catch <#pattern 3#>, <#pattern 4#> where <#condition#> {
|
||||
<#statements#>
|
||||
} catch {
|
||||
<#statements#>
|
||||
}
|
||||
```
|
||||
|
||||
You write a pattern after `catch` to indicate what errors
|
||||
that clause can handle.
|
||||
If a `catch` clause doesn't have a pattern,
|
||||
the clause matches any error
|
||||
and binds the error to a local constant named `error`.
|
||||
For more information about pattern matching,
|
||||
see <doc:Patterns>.
|
||||
|
||||
<!--
|
||||
TODO: Call out the reasoning why we don't let you
|
||||
consider a catch clause exhaustive by just matching
|
||||
the errors in an given enum without a general catch/default.
|
||||
-->
|
||||
|
||||
For example, the following code matches against all three cases
|
||||
of the `VendingMachineError` enumeration.
|
||||
|
||||
```swift
|
||||
var vendingMachine = VendingMachine()
|
||||
vendingMachine.coinsDeposited = 8
|
||||
do {
|
||||
try buyFavoriteSnack(person: "Alice", vendingMachine: vendingMachine)
|
||||
print("Success! Yum.")
|
||||
} catch VendingMachineError.invalidSelection {
|
||||
print("Invalid Selection.")
|
||||
} catch VendingMachineError.outOfStock {
|
||||
print("Out of Stock.")
|
||||
} catch VendingMachineError.insufficientFunds(let coinsNeeded) {
|
||||
print("Insufficient funds. Please insert an additional \(coinsNeeded) coins.")
|
||||
} catch {
|
||||
print("Unexpected error: \(error).")
|
||||
}
|
||||
// Prints "Insufficient funds. Please insert an additional 2 coins."
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `errorHandling`
|
||||
|
||||
```swifttest
|
||||
-> var vendingMachine = VendingMachine()
|
||||
-> vendingMachine.coinsDeposited = 8
|
||||
-> do {
|
||||
try buyFavoriteSnack(person: "Alice", vendingMachine: vendingMachine)
|
||||
print("Success! Yum.")
|
||||
} catch VendingMachineError.invalidSelection {
|
||||
print("Invalid Selection.")
|
||||
} catch VendingMachineError.outOfStock {
|
||||
print("Out of Stock.")
|
||||
} catch VendingMachineError.insufficientFunds(let coinsNeeded) {
|
||||
print("Insufficient funds. Please insert an additional \(coinsNeeded) coins.")
|
||||
} catch {
|
||||
print("Unexpected error: \(error).")
|
||||
}
|
||||
<- Insufficient funds. Please insert an additional 2 coins.
|
||||
```
|
||||
-->
|
||||
|
||||
In the above example,
|
||||
the `buyFavoriteSnack(person:vendingMachine:)` function is called in a `try` expression,
|
||||
because it can throw an error.
|
||||
If an error is thrown,
|
||||
execution immediately transfers to the `catch` clauses,
|
||||
which decide whether to allow propagation to continue.
|
||||
If no pattern is matched, the error gets caught by the final `catch`
|
||||
clause and is bound to a local `error` constant.
|
||||
If no error is thrown,
|
||||
the remaining statements in the `do` statement are executed.
|
||||
|
||||
The `catch` clauses don't have to handle every possible error
|
||||
that the code in the `do` clause can throw.
|
||||
If none of the `catch` clauses handle the error,
|
||||
the error propagates to the surrounding scope.
|
||||
However, the propagated error
|
||||
must be handled by *some* surrounding scope.
|
||||
In a nonthrowing function,
|
||||
an enclosing `do`-`catch` statement
|
||||
must handle the error.
|
||||
In a throwing function,
|
||||
either an enclosing `do`-`catch` statement
|
||||
or the caller
|
||||
must handle the error.
|
||||
If the error propagates to the top-level scope
|
||||
without being handled,
|
||||
you'll get a runtime error.
|
||||
|
||||
For example, the above example can be written so any
|
||||
error that isn't a `VendingMachineError` is instead
|
||||
caught by the calling function:
|
||||
|
||||
```swift
|
||||
func nourish(with item: String) throws {
|
||||
do {
|
||||
try vendingMachine.vend(itemNamed: item)
|
||||
} catch is VendingMachineError {
|
||||
print("Couldn't buy that from the vending machine.")
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
try nourish(with: "Beet-Flavored Chips")
|
||||
} catch {
|
||||
print("Unexpected non-vending-machine-related error: \(error)")
|
||||
}
|
||||
// Prints "Couldn't buy that from the vending machine."
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `errorHandling`
|
||||
|
||||
```swifttest
|
||||
-> func nourish(with item: String) throws {
|
||||
do {
|
||||
try vendingMachine.vend(itemNamed: item)
|
||||
} catch is VendingMachineError {
|
||||
print("Couldn't buy that from the vending machine.")
|
||||
}
|
||||
}
|
||||
|
||||
-> do {
|
||||
try nourish(with: "Beet-Flavored Chips")
|
||||
} catch {
|
||||
print("Unexpected non-vending-machine-related error: \(error)")
|
||||
}
|
||||
<- Couldn't buy that from the vending machine.
|
||||
```
|
||||
-->
|
||||
|
||||
In the `nourish(with:)` function,
|
||||
if `vend(itemNamed:)` throws an error that's
|
||||
one of the cases of the `VendingMachineError` enumeration,
|
||||
`nourish(with:)` handles the error by printing a message.
|
||||
Otherwise,
|
||||
`nourish(with:)` propagates the error to its call site.
|
||||
The error is then caught by the general `catch` clause.
|
||||
|
||||
Another way to catch several related errors
|
||||
is to list them after `catch`, separated by commas.
|
||||
For example:
|
||||
|
||||
```swift
|
||||
func eat(item: String) throws {
|
||||
do {
|
||||
try vendingMachine.vend(itemNamed: item)
|
||||
} catch VendingMachineError.invalidSelection, VendingMachineError.insufficientFunds, VendingMachineError.outOfStock {
|
||||
print("Invalid selection, out of stock, or not enough money.")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `errorHandling`
|
||||
|
||||
```swifttest
|
||||
-> func eat(item: String) throws {
|
||||
do {
|
||||
try vendingMachine.vend(itemNamed: item)
|
||||
} catch VendingMachineError.invalidSelection, VendingMachineError.insufficientFunds, VendingMachineError.outOfStock {
|
||||
print("Invalid selection, out of stock, or not enough money.")
|
||||
}
|
||||
}
|
||||
>> do {
|
||||
>> try eat(item: "Beet-Flavored Chips")
|
||||
>> } catch {
|
||||
>> print("Unexpected error: \(error)")
|
||||
>> }
|
||||
<< Invalid selection, out of stock, or not enough money.
|
||||
```
|
||||
-->
|
||||
|
||||
<!--
|
||||
FIXME the catch clause is getting indented oddly in HTML output if I hard wrap it
|
||||
-->
|
||||
|
||||
The `eat(item:)` function lists the vending machine errors to catch,
|
||||
and its error text corresponds to the items in that list.
|
||||
If any of the three listed errors are thrown,
|
||||
this `catch` clause handles them by printing a message.
|
||||
Any other errors are propagated to the surrounding scope,
|
||||
including any vending-machine errors that might be added later.
|
||||
|
||||
### Converting Errors to Optional Values
|
||||
|
||||
You use `try?` to handle an error by converting it to an optional value.
|
||||
If an error is thrown while evaluating the `try?` expression,
|
||||
the value of the expression is `nil`.
|
||||
For example,
|
||||
in the following code `x` and `y` have the same value and behavior:
|
||||
|
||||
```swift
|
||||
func someThrowingFunction() throws -> Int {
|
||||
// ...
|
||||
}
|
||||
|
||||
let x = try? someThrowingFunction()
|
||||
|
||||
let y: Int?
|
||||
do {
|
||||
y = try someThrowingFunction()
|
||||
} catch {
|
||||
y = nil
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `optional-try`
|
||||
|
||||
```swifttest
|
||||
-> func someThrowingFunction() throws -> Int {
|
||||
// ...
|
||||
>> return 40
|
||||
-> }
|
||||
|
||||
-> let x = try? someThrowingFunction()
|
||||
>> print(x as Any)
|
||||
<< Optional(40)
|
||||
|
||||
-> let y: Int?
|
||||
do {
|
||||
y = try someThrowingFunction()
|
||||
} catch {
|
||||
y = nil
|
||||
}
|
||||
>> print(y as Any)
|
||||
<< Optional(40)
|
||||
```
|
||||
-->
|
||||
|
||||
If `someThrowingFunction()` throws an error,
|
||||
the value of `x` and `y` is `nil`.
|
||||
Otherwise, the value of `x` and `y` is the value that the function returned.
|
||||
Note that `x` and `y` are an optional of whatever type `someThrowingFunction()` returns.
|
||||
Here the function returns an integer, so `x` and `y` are optional integers.
|
||||
|
||||
Using `try?` lets you write concise error handling code
|
||||
when you want to handle all errors in the same way.
|
||||
For example,
|
||||
the following code
|
||||
uses several approaches to fetch data,
|
||||
or returns `nil` if all of the approaches fail.
|
||||
|
||||
```swift
|
||||
func fetchData() -> Data? {
|
||||
if let data = try? fetchDataFromDisk() { return data }
|
||||
if let data = try? fetchDataFromServer() { return data }
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `optional-try-cached-data`
|
||||
|
||||
```swifttest
|
||||
>> struct Data {}
|
||||
>> func fetchDataFromDisk() throws -> Data { return Data() }
|
||||
>> func fetchDataFromServer() throws -> Data { return Data() }
|
||||
-> func fetchData() -> Data? {
|
||||
if let data = try? fetchDataFromDisk() { return data }
|
||||
if let data = try? fetchDataFromServer() { return data }
|
||||
return nil
|
||||
}
|
||||
```
|
||||
-->
|
||||
|
||||
### Disabling Error Propagation
|
||||
|
||||
Sometimes you know a throwing function or method
|
||||
won't, in fact, throw an error at runtime.
|
||||
On those occasions,
|
||||
you can write `try!` before the expression to disable error propagation
|
||||
and wrap the call in a runtime assertion that no error will be thrown.
|
||||
If an error actually is thrown, you'll get a runtime error.
|
||||
|
||||
For example, the following code uses a `loadImage(atPath:)` function,
|
||||
which loads the image resource at a given path
|
||||
or throws an error if the image can't be loaded.
|
||||
In this case, because the image is shipped with the application,
|
||||
no error will be thrown at runtime,
|
||||
so it's appropriate to disable error propagation.
|
||||
|
||||
```swift
|
||||
let photo = try! loadImage(atPath: "./Resources/John Appleseed.jpg")
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `forceTryStatement`
|
||||
|
||||
```swifttest
|
||||
>> struct Image {}
|
||||
>> func loadImage(atPath path: String) throws -> Image {
|
||||
>> return Image()
|
||||
>> }
|
||||
-> let photo = try! loadImage(atPath: "./Resources/John Appleseed.jpg")
|
||||
```
|
||||
-->
|
||||
|
||||
## Specifying the Error Type
|
||||
|
||||
All of the examples above use the most common kind of error handling,
|
||||
where the errors that your code throws
|
||||
can be values of any type that conforms to the `Error` protocol.
|
||||
This approach matches the reality that
|
||||
you don't know ahead of time every error that could happen
|
||||
while the code is running,
|
||||
especially when propagating errors thrown somewhere else.
|
||||
It also reflects the fact that errors can change over time.
|
||||
New versions of a library ---
|
||||
including libraries that your dependencies use ---
|
||||
can throw new errors,
|
||||
and the rich complexity of real-world user configurations
|
||||
can expose failure modes that weren't visible during development or testing.
|
||||
The error handling code in the examples above
|
||||
always includes a default case to handle errors
|
||||
that don't have a specific `catch` clause.
|
||||
|
||||
Most Swift code doesn't specify the type for the errors it throws.
|
||||
However,
|
||||
you might limit code to throwing errors of only one specific type
|
||||
in the following special cases:
|
||||
|
||||
- When running code on an embedded system
|
||||
that doesn't support dynamic allocation of memory.
|
||||
Throwing an instance of `any Error` or another boxed protocol type
|
||||
requires allocating memory at runtime to store the error.
|
||||
In contrast,
|
||||
throwing an error of a specific type
|
||||
lets Swift avoid heap allocation for errors.
|
||||
|
||||
- When the errors are an implementation detail of some unit of code,
|
||||
like a library,
|
||||
and aren't part of the interface to that code.
|
||||
Because the errors come from only the library,
|
||||
and not from other dependencies or the library's clients,
|
||||
you can make an exhaustive list of all possible failures.
|
||||
And because these errors are an implementation detail of the library,
|
||||
they're always handled within that library.
|
||||
|
||||
- In code that only propagates errors described by generic parameters,
|
||||
like a function that takes a closure argument
|
||||
and propagates any errors from that closure.
|
||||
For a comparison between propagating a specific error type
|
||||
and using `rethrows`,
|
||||
see <doc:Declarations#Rethrowing-Functions-and-Methods>.
|
||||
|
||||
For example,
|
||||
consider code that summarizes ratings
|
||||
and uses the following error type:
|
||||
|
||||
```swift
|
||||
enum StatisticsError: Error {
|
||||
case noRatings
|
||||
case invalidRating(Int)
|
||||
}
|
||||
```
|
||||
|
||||
To specify that a function throws only `StatisticsError` values as its errors,
|
||||
you write `throws(StatisticsError)` instead of only `throws`
|
||||
when declaring the function.
|
||||
This syntax is also called *typed throws*
|
||||
because you write the error type after `throws` in the declaration.
|
||||
For example,
|
||||
the function below throws `StatisticsError` values as its errors.
|
||||
|
||||
```swift
|
||||
func summarize(_ ratings: [Int]) throws(StatisticsError) {
|
||||
guard !ratings.isEmpty else { throw .noRatings }
|
||||
|
||||
var counts = [1: 0, 2: 0, 3: 0]
|
||||
for rating in ratings {
|
||||
guard rating > 0 && rating <= 3 else { throw .invalidRating(rating) }
|
||||
counts[rating]! += 1
|
||||
}
|
||||
|
||||
print("*", counts[1]!, "-- **", counts[2]!, "-- ***", counts[3]!)
|
||||
}
|
||||
```
|
||||
|
||||
In the code above,
|
||||
the `summarize(_:)` function summarizes a list of ratings
|
||||
expressed on a scale of 1 to 3.
|
||||
This function throws an instance of `StatisticsError` if the input isn't valid.
|
||||
Both places in the code above that throw an error
|
||||
omit the type of the error
|
||||
because the function's error type is already defined.
|
||||
You can use the short form, `throw .noRatings`,
|
||||
instead of writing `throw StatisticsError.noRatings`
|
||||
when throwing an error in a function like this.
|
||||
|
||||
When you write a specific error type at the start of the function,
|
||||
Swift checks that you don't throw any other errors.
|
||||
For example,
|
||||
if you tried to use `VendingMachineError` from examples earlier in this chapter
|
||||
in the `summarize(_:)` function above,
|
||||
that code would produce an error at compile time.
|
||||
|
||||
You can call a function that uses typed throws
|
||||
from within a regular throwing function:
|
||||
|
||||
```swift
|
||||
func someThrowingFunction() throws {
|
||||
let ratings = [1, 2, 3, 2, 2, 1]
|
||||
try summarize(ratings)
|
||||
}
|
||||
```
|
||||
|
||||
The code above doesn't specify an error type for `someThrowingFunction()`,
|
||||
so it throws `any Error`.
|
||||
You could also write the error type explicitly as `throws(any Error)`;
|
||||
the code below is equivalent to the code above:
|
||||
|
||||
```swift
|
||||
func someThrowingFunction() throws(any Error) {
|
||||
let ratings = [1, 2, 3, 2, 2, 1]
|
||||
try summarize(ratings)
|
||||
}
|
||||
```
|
||||
|
||||
In this code,
|
||||
`someThrowingFunction()` propagates any errors that `summarize(_:)` throws.
|
||||
The errors from `summarize(_:)` are always `StatisticsError` values,
|
||||
which is also a valid error for `someThrowingFunction()` to throw.
|
||||
|
||||
Just like you can write a function that never returns
|
||||
with a return type of `Never`,
|
||||
you can write a function that never throws with `throws(Never)`:
|
||||
|
||||
```swift
|
||||
func nonThrowingFunction() throws(Never) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
This function can't throw because
|
||||
it's impossible to create a value of type `Never` to throw.
|
||||
|
||||
In addition to specifying a function's error type,
|
||||
you can also write a specific error type for a `do`-`catch` statement.
|
||||
For example:
|
||||
|
||||
```swift
|
||||
let ratings = []
|
||||
do throws(StatisticsError) {
|
||||
try summarize(ratings)
|
||||
} catch {
|
||||
switch error {
|
||||
case .noRatings:
|
||||
print("No ratings available")
|
||||
case .invalidRating(let rating):
|
||||
print("Invalid rating: \(rating)")
|
||||
}
|
||||
}
|
||||
// Prints "No ratings available".
|
||||
```
|
||||
|
||||
In this code,
|
||||
writing `do throws(StatisticsError)` indicates that
|
||||
the `do`-`catch` statement throws `StatisticsError` values as its errors.
|
||||
Like other `do`-`catch` statements,
|
||||
the `catch` clause can either handle every possible error
|
||||
or propagate unhandled errors for some surrounding scope to handle.
|
||||
This code handles all of the errors,
|
||||
using a `switch` statement with one case for each enumeration value.
|
||||
Like other `catch` clauses that don't have a pattern,
|
||||
the clause matches any error
|
||||
and binds the error to a local constant named `error`.
|
||||
Because the `do`-`catch` statement throws `StatisticsError` values,
|
||||
`error` is a value of type `StatisticsError`.
|
||||
|
||||
The `catch` clause above uses a `switch` statement
|
||||
to match and handle each possible error.
|
||||
If you tried to add a new case to `StatisticsError`
|
||||
without updating the error-handling code,
|
||||
Swift would give you an error
|
||||
because the `switch` statement wouldn't be exhaustive anymore.
|
||||
For a library that catches all of its own errors,
|
||||
you could use this approach to ensure any new errors
|
||||
get corresponding new code to handle them.
|
||||
|
||||
If a function or `do` block throws errors of only a single type,
|
||||
Swift infers that this code is using typed throws.
|
||||
Using this shorter syntax,
|
||||
you could write the `do`-`catch` example above as follows:
|
||||
|
||||
```swift
|
||||
let ratings = []
|
||||
do {
|
||||
try summarize(ratings)
|
||||
} catch {
|
||||
switch error {
|
||||
case .noRatings:
|
||||
print("No ratings available")
|
||||
case .invalidRating(let rating):
|
||||
print("Invalid rating: \(rating)")
|
||||
}
|
||||
}
|
||||
// Prints "No ratings available".
|
||||
```
|
||||
|
||||
Even though the `do`-`catch` block above
|
||||
doesn't specify what type of error it throws,
|
||||
Swift infers that it throws `StatisticsError`.
|
||||
You can explicitly write `throws(any Error)`
|
||||
to avoid letting Swift infer typed throws.
|
||||
|
||||
## Specifying Cleanup Actions
|
||||
|
||||
You use a `defer` statement to execute a set of statements
|
||||
just before code execution leaves the current block of code.
|
||||
This statement lets you do any necessary cleanup
|
||||
that should be performed regardless
|
||||
of *how* execution leaves the current block of code ---
|
||||
whether it leaves because an error was thrown
|
||||
or because of a statement such as `return` or `break`.
|
||||
For example, you can use a `defer` statement
|
||||
to ensure that file descriptors are closed
|
||||
and manually allocated memory is freed.
|
||||
|
||||
A `defer` statement defers execution until the current scope is exited.
|
||||
This statement consists of the `defer` keyword and the statements to be executed later.
|
||||
The deferred statements may not contain any code
|
||||
that would transfer control out of the statements,
|
||||
such as a `break` or a `return` statement,
|
||||
or by throwing an error.
|
||||
Deferred actions are executed in the reverse of
|
||||
the order that they're written in your source code.
|
||||
That is, the code in the first `defer` statement executes last,
|
||||
the code in the second `defer` statement executes second to last,
|
||||
and so on.
|
||||
The last `defer` statement in source code order executes first.
|
||||
|
||||
```swift
|
||||
func processFile(filename: String) throws {
|
||||
if exists(filename) {
|
||||
let file = open(filename)
|
||||
defer {
|
||||
close(file)
|
||||
}
|
||||
while let line = try file.readline() {
|
||||
// Work with the file.
|
||||
}
|
||||
// close(file) is called here, at the end of the scope.
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
- test: `defer`
|
||||
|
||||
```swifttest
|
||||
>> func exists(_ file: String) -> Bool { return true }
|
||||
>> struct File {
|
||||
>> func readline() throws -> String? { return nil }
|
||||
>> }
|
||||
>> func open(_ file: String) -> File { return File() }
|
||||
>> func close(_ fileHandle: File) {}
|
||||
-> func processFile(filename: String) throws {
|
||||
if exists(filename) {
|
||||
let file = open(filename)
|
||||
defer {
|
||||
close(file)
|
||||
}
|
||||
while let line = try file.readline() {
|
||||
// Work with the file.
|
||||
>> print(line)
|
||||
}
|
||||
// close(file) is called here, at the end of the scope.
|
||||
}
|
||||
}
|
||||
```
|
||||
-->
|
||||
|
||||
The above example uses a `defer` statement
|
||||
to ensure that the `open(_:)` function
|
||||
has a corresponding call to `close(_:)`.
|
||||
|
||||
You can use a `defer` statement
|
||||
even when no error handling code is involved.
|
||||
For more information,
|
||||
see <doc:ControlFlow#Deferred-Actions>.
|
||||
|
||||
<!--
|
||||
This source file is part of the Swift.org open source project
|
||||
|
||||
Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors
|
||||
Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
|
||||
See https://swift.org/LICENSE.txt for license information
|
||||
See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
||||
-->
|
||||
Reference in New Issue
Block a user