Files
gh-kylehughes-the-unofficia…/skills/programming-swift/LanguageGuide/ErrorHandling.md
2025-11-30 08:36:15 +08:00

994 lines
30 KiB
Markdown

# 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
-->