diff --git a/TSPL.docc/LanguageGuide/ErrorHandling.md b/TSPL.docc/LanguageGuide/ErrorHandling.md index f76189d71..661ca1d02 100644 --- a/TSPL.docc/LanguageGuide/ErrorHandling.md +++ b/TSPL.docc/LanguageGuide/ErrorHandling.md @@ -699,6 +699,213 @@ 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 . + +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 diff --git a/TSPL.docc/ReferenceManual/Declarations.md b/TSPL.docc/ReferenceManual/Declarations.md index bc3b715b6..d0e5bdfb7 100644 --- a/TSPL.docc/ReferenceManual/Declarations.md +++ b/TSPL.docc/ReferenceManual/Declarations.md @@ -1465,13 +1465,25 @@ func <#function name#>(<#parameters#>) throws -> <#return type#> { } ``` +A function that throws a specific error type has the following form: + +```swift +func <#function name#>(<#parameters#>) throws(<#error type#>) -> <#return type#> { + <#statements#> +} +``` + Calls to a throwing function or method must be wrapped in a `try` or `try!` expression (that is, in the scope of a `try` or `try!` operator). -The `throws` keyword is part of a function's type, -and nonthrowing functions are subtypes of throwing functions. -As a result, you can use a nonthrowing function +A function's type includes whether it can throw an error +and what type of error it throws. +This subtype relationship means, for example, you can use a nonthrowing function in a context where a throwing one is expected. +For more information about the type of a throwing function, +see . +For examples of working with errors that have explicit types, +see . You can't overload a function based only on whether the function can throw an error. That said, @@ -1582,6 +1594,28 @@ and a throwing method can't satisfy a protocol requirement for a rethrowing meth That said, a rethrowing method can override a throwing method, and a rethrowing method can satisfy a protocol requirement for a throwing method. +An alternative to rethrowing is throwing a specific error type in generic code. +For example: + +```swift +func someFunction(callback: () throws(E) -> Void) throws(E) { + try callback() +} +``` + +This approach to propagating an error +preserves type information about the error. +However, unlike marking a function `rethrows`, +this approach doesn't prevent the function +from throwing an error of the same type. + + + ### Asynchronous Functions and Methods Functions and methods that run asynchronously must be marked with the `async` keyword. @@ -1660,7 +1694,7 @@ but the new method must preserve its return type and nonreturning behavior. > *function-head* → *attributes*_?_ *declaration-modifiers*_?_ **`func`** \ > *function-name* → *identifier* | *operator* > -> *function-signature* → *parameter-clause* **`async`**_?_ **`throws`**_?_ *function-result*_?_ \ +> *function-signature* → *parameter-clause* **`async`**_?_ *throws-clause*_?_ *function-result*_?_ \ > *function-signature* → *parameter-clause* **`async`**_?_ **`rethrows`** *function-result*_?_ \ > *function-result* → **`->`** *attributes*_?_ *type* \ > *function-body* → *code-block* @@ -2558,7 +2592,7 @@ See also . > Grammar of a protocol initializer declaration: > -> *protocol-initializer-declaration* → *initializer-head* *generic-parameter-clause*_?_ *parameter-clause* **`throws`**_?_ *generic-where-clause*_?_ \ +> *protocol-initializer-declaration* → *initializer-head* *generic-parameter-clause*_?_ *parameter-clause* *throws-clause*_?_ *generic-where-clause*_?_ \ > *protocol-initializer-declaration* → *initializer-head* *generic-parameter-clause*_?_ *parameter-clause* **`rethrows`** *generic-where-clause*_?_ ### Protocol Subscript Declaration @@ -2903,7 +2937,7 @@ see . > Grammar of an initializer declaration: > -> *initializer-declaration* → *initializer-head* *generic-parameter-clause*_?_ *parameter-clause* **`async`**_?_ **`throws`**_?_ *generic-where-clause*_?_ *initializer-body* \ +> *initializer-declaration* → *initializer-head* *generic-parameter-clause*_?_ *parameter-clause* **`async`**_?_ *throws-clause*_?_ *generic-where-clause*_?_ *initializer-body* \ > *initializer-declaration* → *initializer-head* *generic-parameter-clause*_?_ *parameter-clause* **`async`**_?_ **`rethrows`** *generic-where-clause*_?_ *initializer-body* \ > *initializer-head* → *attributes*_?_ *declaration-modifiers*_?_ **`init`** \ > *initializer-head* → *attributes*_?_ *declaration-modifiers*_?_ **`init`** **`?`** \ diff --git a/TSPL.docc/ReferenceManual/Expressions.md b/TSPL.docc/ReferenceManual/Expressions.md index 370450ea8..eac7bc81a 100644 --- a/TSPL.docc/ReferenceManual/Expressions.md +++ b/TSPL.docc/ReferenceManual/Expressions.md @@ -922,9 +922,13 @@ explicitly marks a closure as throwing or asynchronous. } ``` -If the body of a closure includes a try expression, +If the body of a closure includes a `throws` statement or a `try` expression +that isn't nested inside of a `do` statement with exhaustive error handling, the closure is understood to be throwing. -Likewise, if it includes an await expression, +If a throwing closure throws errors of only a single type, +the closure is understood as throwing that error type; +otherwise, it's understood as throwing `any Error`. +Likewise, if the body includes an `await` expression, it's understood to be asynchronous. There are several special forms @@ -1245,7 +1249,7 @@ see > *closure-expression* → **`{`** *attributes*_?_ *closure-signature*_?_ *statements*_?_ **`}`** > -> *closure-signature* → *capture-list*_?_ *closure-parameter-clause* **`async`**_?_ **`throws`**_?_ *function-result*_?_ **`in`** \ +> *closure-signature* → *capture-list*_?_ *closure-parameter-clause* **`async`**_?_ *throws-clause*_?_ *function-result*_?_ **`in`** \ > *closure-signature* → *capture-list* **`in`** > > *closure-parameter-clause* → **`(`** **`)`** | **`(`** *closure-parameter-list* **`)`** | *identifier-list* \ diff --git a/TSPL.docc/ReferenceManual/Statements.md b/TSPL.docc/ReferenceManual/Statements.md index 7add92d3c..c796b4321 100644 --- a/TSPL.docc/ReferenceManual/Statements.md +++ b/TSPL.docc/ReferenceManual/Statements.md @@ -729,6 +729,9 @@ throw <#expression#> The value of the *expression* must have a type that conforms to the `Error` protocol. +If the `do` statement or function that contains the `throw` statement +declares the type of errors it throws, +the value of the *expression* must be an instance of that type. For an example of how to use a `throw` statement, see @@ -873,6 +876,46 @@ do { } ``` +A `do` statement can optionally specify the type of error it throws, +which has the following form: + +```swift +do throws(<#type#>) { + try <#expression#> +} catch <#pattern> { + <#statements#> +} catch { + <#statements#> +} +``` + +If the `do` statement includes a `throws` clause, +the `do` block can throw errors of only the specified *type*. +The *type* must be +a concrete type that conforms to the `Error` protocol, +an opaque type that conforms to the `Error` protocol, +or the boxed protocol type `any Error`. +If the `do` statement doesn't specify the type of error it throws, +Swift infers the error type as follows: + +- If every `throws` statement and `try` expression in the `do` code block + is nested inside of an exhaustive error-handling mechanism, + then Swift infers that the `do` statement is nonthrowing. + +- If the `do` code block contains code that throws + errors of only a single type + outside of exhaustive error handling, + other than throwing `Never`, + then Swift infers that the `do` statement throws that concrete error type. + +- If the `do` code block contains code that throws + errors of more than a single type + outside of exhaustive error handling, + then Swift infers that the `do` statement throws `any Error`. + +For more information about working with errors that have explicit types, +see . + If any statement in the `do` code block throws an error, program control is transferred to the first `catch` clause whose pattern matches the error. @@ -914,7 +957,7 @@ see . > Grammar of a do statement: > -> *do-statement* → **`do`** *code-block* *catch-clauses*_?_ \ +> *do-statement* → **`do`** *throws-clause*_?_ *code-block* *catch-clauses*_?_ \ > *catch-clauses* → *catch-clause* *catch-clauses*_?_ \ > *catch-clause* → **`catch`** *catch-pattern-list*_?_ *code-block* \ > *catch-pattern-list* → *catch-pattern* | *catch-pattern* **`,`** *catch-pattern-list* \ diff --git a/TSPL.docc/ReferenceManual/SummaryOfTheGrammar.md b/TSPL.docc/ReferenceManual/SummaryOfTheGrammar.md index 18f87e531..f6b3d48ca 100644 --- a/TSPL.docc/ReferenceManual/SummaryOfTheGrammar.md +++ b/TSPL.docc/ReferenceManual/SummaryOfTheGrammar.md @@ -254,7 +254,7 @@ make the same change here also. > Grammar of a function type: > -> *function-type* → *attributes*_?_ *function-type-argument-clause* **`async`**_?_ **`throws`**_?_ **`->`** *type* +> *function-type* → *attributes*_?_ *function-type-argument-clause* **`async`**_?_ *throws-clause*_?_ **`->`** *type* > > *function-type-argument-clause* → **`(`** **`)`** \ > *function-type-argument-clause* → **`(`** *function-type-argument-list* **`...`**_?_ **`)`** @@ -262,6 +262,8 @@ make the same change here also. > *function-type-argument-list* → *function-type-argument* | *function-type-argument* **`,`** *function-type-argument-list* \ > *function-type-argument* → *attributes*_?_ **`inout`**_?_ *type* | *argument-label* *type-annotation* \ > *argument-label* → *identifier* +> +> *throws-clause* → **`throws`** | **`throws`** **`(`** *type* **`)`** > Grammar of an array type: > @@ -422,7 +424,7 @@ make the same change here also. > > *closure-expression* → **`{`** *attributes*_?_ *closure-signature*_?_ *statements*_?_ **`}`** > -> *closure-signature* → *capture-list*_?_ *closure-parameter-clause* **`async`**_?_ **`throws`**_?_ *function-result*_?_ **`in`** \ +> *closure-signature* → *capture-list*_?_ *closure-parameter-clause* **`async`**_?_ *throws-clause*_?_ *function-result*_?_ **`in`** \ > *closure-signature* → *capture-list* **`in`** > > *closure-parameter-clause* → **`(`** **`)`** | **`(`** *closure-parameter-list* **`)`** | *identifier-list* \ @@ -656,7 +658,7 @@ make the same change here also. > Grammar of a do statement: > -> *do-statement* → **`do`** *code-block* *catch-clauses*_?_ \ +> *do-statement* → **`do`** *throws-clause*_?_ *code-block* *catch-clauses*_?_ \ > *catch-clauses* → *catch-clause* *catch-clauses*_?_ \ > *catch-clause* → **`catch`** *catch-pattern-list*_?_ *code-block* \ > *catch-pattern-list* → *catch-pattern* | *catch-pattern* **`,`** *catch-pattern-list* \ @@ -813,7 +815,7 @@ make the same change here also. > *function-head* → *attributes*_?_ *declaration-modifiers*_?_ **`func`** \ > *function-name* → *identifier* | *operator* > -> *function-signature* → *parameter-clause* **`async`**_?_ **`throws`**_?_ *function-result*_?_ \ +> *function-signature* → *parameter-clause* **`async`**_?_ *throws-clause*_?_ *function-result*_?_ \ > *function-signature* → *parameter-clause* **`async`**_?_ **`rethrows`** *function-result*_?_ \ > *function-result* → **`->`** *attributes*_?_ *type* \ > *function-body* → *code-block* @@ -907,7 +909,7 @@ make the same change here also. > Grammar of a protocol initializer declaration: > -> *protocol-initializer-declaration* → *initializer-head* *generic-parameter-clause*_?_ *parameter-clause* **`throws`**_?_ *generic-where-clause*_?_ \ +> *protocol-initializer-declaration* → *initializer-head* *generic-parameter-clause*_?_ *parameter-clause* *throws-clause*_?_ *generic-where-clause*_?_ \ > *protocol-initializer-declaration* → *initializer-head* *generic-parameter-clause*_?_ *parameter-clause* **`rethrows`** *generic-where-clause*_?_ > Grammar of a protocol subscript declaration: @@ -920,7 +922,7 @@ make the same change here also. > Grammar of an initializer declaration: > -> *initializer-declaration* → *initializer-head* *generic-parameter-clause*_?_ *parameter-clause* **`async`**_?_ **`throws`**_?_ *generic-where-clause*_?_ *initializer-body* \ +> *initializer-declaration* → *initializer-head* *generic-parameter-clause*_?_ *parameter-clause* **`async`**_?_ *throws-clause*_?_ *generic-where-clause*_?_ *initializer-body* \ > *initializer-declaration* → *initializer-head* *generic-parameter-clause*_?_ *parameter-clause* **`async`**_?_ **`rethrows`** *generic-where-clause*_?_ *initializer-body* \ > *initializer-head* → *attributes*_?_ *declaration-modifiers*_?_ **`init`** \ > *initializer-head* → *attributes*_?_ *declaration-modifiers*_?_ **`init`** **`?`** \ diff --git a/TSPL.docc/ReferenceManual/Types.md b/TSPL.docc/ReferenceManual/Types.md index b65112dfe..c22299b07 100644 --- a/TSPL.docc/ReferenceManual/Types.md +++ b/TSPL.docc/ReferenceManual/Types.md @@ -347,10 +347,42 @@ that is, a function that takes an `Int` and returns another function that takes and returns an `Int`. Function types for functions -that can throw or rethrow an error must be marked with the `throws` keyword. -The `throws` keyword is part of a function's type, -and nonthrowing functions are subtypes of throwing functions. -As a result, you can use a nonthrowing function in the same places as a throwing one. +that can throw or rethrow an error must include the `throws` keyword. +You can include a type after `throws` in parentheses +to specify the type of error that the function throws. +The throw error type must conform to the `Error` protocol. +Writing `throws` without specifying a type +is the same as writing `throws(any Error)`. +Omitting `throws` is the same as writing `throws(Never)`. +The error type that a function throws +can be any type that conforms to `Error`, +including generic types, boxed protocol types, and opaque types. + +The type of error that a function throws is part of that function's type, +and a subtype relationship between error types +means the corresponding function types are also subtypes. +For example, if you declare a custom `MyError` type, +the relationship between some function types is as follows, +from supertype to subtype: + +1. Functions that throw any error, marked `throws(any Error)` +1. Functions that throw a specific error, marked `throws(MyError)` +1. Functions that don't throw, marked `throws(Never)` + +As a result of these subtype relationships: + +- You can use a nonthrowing function + in the same places as a throwing function. +- You can use a function that throws a concrete error type + in the same places as a throwing function. +- You can use a function that throws a more specific error type + in the same places as a function that throws a more general error type. + +If you use an associated type or a generic type parameter +as the thrown error type in a function type, +then that associated type or generic type parameter +is implicitly required to conform to the `Error` protocol. + Throwing and rethrowing functions are described in and . @@ -476,7 +508,7 @@ see . > Grammar of a function type: > -> *function-type* → *attributes*_?_ *function-type-argument-clause* **`async`**_?_ **`throws`**_?_ **`->`** *type* +> *function-type* → *attributes*_?_ *function-type-argument-clause* **`async`**_?_ *throws-clause*_?_ **`->`** *type* > > *function-type-argument-clause* → **`(`** **`)`** \ > *function-type-argument-clause* → **`(`** *function-type-argument-list* **`...`**_?_ **`)`** @@ -484,6 +516,8 @@ see . > *function-type-argument-list* → *function-type-argument* | *function-type-argument* **`,`** *function-type-argument-list* \ > *function-type-argument* → *attributes*_?_ *parameter-modifier*_?_ *type* | *argument-label* *type-annotation* \ > *argument-label* → *identifier* +> +> *throws-clause* → **`throws`** | **`throws`** **`(`** *type* **`)`**