From 2140ed7def1de525bd18ac805f356f036271df88 Mon Sep 17 00:00:00 2001 From: Alex Martini Date: Mon, 26 Feb 2024 15:54:09 -0800 Subject: [PATCH 01/20] Update grammar to include typed throws --- TSPL.docc/ReferenceManual/Declarations.md | 6 +++--- TSPL.docc/ReferenceManual/Expressions.md | 2 +- TSPL.docc/ReferenceManual/Statements.md | 2 +- TSPL.docc/ReferenceManual/SummaryOfTheGrammar.md | 14 ++++++++------ TSPL.docc/ReferenceManual/Types.md | 4 +++- 5 files changed, 16 insertions(+), 12 deletions(-) diff --git a/TSPL.docc/ReferenceManual/Declarations.md b/TSPL.docc/ReferenceManual/Declarations.md index bc3b715b6..c6ea628f5 100644 --- a/TSPL.docc/ReferenceManual/Declarations.md +++ b/TSPL.docc/ReferenceManual/Declarations.md @@ -1660,7 +1660,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 +2558,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 +2903,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..5bbee8763 100644 --- a/TSPL.docc/ReferenceManual/Expressions.md +++ b/TSPL.docc/ReferenceManual/Expressions.md @@ -1245,7 +1245,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..f1be3eacd 100644 --- a/TSPL.docc/ReferenceManual/Statements.md +++ b/TSPL.docc/ReferenceManual/Statements.md @@ -914,7 +914,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..03235daac 100644 --- a/TSPL.docc/ReferenceManual/Types.md +++ b/TSPL.docc/ReferenceManual/Types.md @@ -476,7 +476,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 +484,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* **`)`** . + 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. From 7ecd269cad6f7e597ae128a609f0766f92559608 Mon Sep 17 00:00:00 2001 From: Alex Martini Date: Thu, 29 Feb 2024 15:20:30 -0800 Subject: [PATCH 03/20] Start outlining typed throws in the guide --- TSPL.docc/LanguageGuide/ErrorHandling.md | 88 ++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/TSPL.docc/LanguageGuide/ErrorHandling.md b/TSPL.docc/LanguageGuide/ErrorHandling.md index f76189d71..a5ebc96f9 100644 --- a/TSPL.docc/LanguageGuide/ErrorHandling.md +++ b/TSPL.docc/LanguageGuide/ErrorHandling.md @@ -699,6 +699,94 @@ let photo = try! loadImage(atPath: "./Resources/John Appleseed.jpg") ``` --> +## Specifying a Concrete Error Type + +XXX OUTLINE XXX + +- Most code that throws errors just writes `throws`. + This is the same as writing `throws(any Error)`. + However, you can write `throws(SomeErrorType)` + to throw errors of a specific concrete type. + +- What is a boxed protocol (aka existential) error type? + +- What is a concrete error type? + +- Because you write a type after `throws` + this syntax is also called "typed throws". + +- You can also use opaque types like `throws(some MyErrorProtocol)` -- + this is still "concrete" in sense that + the errors are all instances of the concrete type + that's hidden behind the opaque type. + +- When should I use concrete error types? + (See SE-0413 for a list.) + Why shouldn't I just use this everywhere? + +- How do I write a concrete error type for a `do` block? + +- How do I write a concrete error type for a throwing function? + +- What's the type-system relationship + between plain `throws` and `throws(SomeErrorType)`? + When can I interchange them? + +- If a function or `do` block throws only errors of a single type, + the compiler infers that as the concrete error type. + You can explicitly write `throws(any Error` to suppress that. + +- How do I exhaustively handle errors of a concrete type in a `catch` block? + + + +XXX RUNNING EXAMPLE XXX + +```swift +enum StatisticsError: Error { + case noRatings + case invalidRating(Int) +} + +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("One star:", counts[1]!) + print("Two stars:", counts[2]!) + print("Three stars:", counts[3]!) + + print("*", counts[1]!, "-- **", counts[2]!, "-- ***", counts[3]!) +} + +func printSummary(_ ratings: [Int]) { + do { + try summarize(ratings) + } catch { + switch error { + case .noRatings: + print("No ratings available") + case .invalidRating(let rating): + print("Invalid rating: \(rating)") + } + } +} + +printSummary([1, 2, 3, 2, 2, 1]) +printSummary([]) +printSummary([1, 100]) +``` + ## Specifying Cleanup Actions You use a `defer` statement to execute a set of statements From efefc23ccdf7ddc0a3247469e08172a9c3e0be15 Mon Sep 17 00:00:00 2001 From: Alex Martini Date: Fri, 8 Mar 2024 15:09:12 -0800 Subject: [PATCH 04/20] Mark updates needed in the reference --- TSPL.docc/ReferenceManual/Declarations.md | 8 ++++++++ TSPL.docc/ReferenceManual/Expressions.md | 4 ++++ TSPL.docc/ReferenceManual/Statements.md | 2 +- TSPL.docc/ReferenceManual/Types.md | 8 ++++++++ 4 files changed, 21 insertions(+), 1 deletion(-) diff --git a/TSPL.docc/ReferenceManual/Declarations.md b/TSPL.docc/ReferenceManual/Declarations.md index c6ea628f5..58a56edd7 100644 --- a/TSPL.docc/ReferenceManual/Declarations.md +++ b/TSPL.docc/ReferenceManual/Declarations.md @@ -1468,8 +1468,16 @@ func <#function name#>(<#parameters#>) throws -> <#return type#> { 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 in a context where a throwing one is expected. diff --git a/TSPL.docc/ReferenceManual/Expressions.md b/TSPL.docc/ReferenceManual/Expressions.md index 5bbee8763..1b794f5ec 100644 --- a/TSPL.docc/ReferenceManual/Expressions.md +++ b/TSPL.docc/ReferenceManual/Expressions.md @@ -924,6 +924,10 @@ explicitly marks a closure as throwing or asynchronous. If the body of a closure includes a try expression, the closure is understood to be throwing. + Likewise, if it includes an await expression, it's understood to be asynchronous. diff --git a/TSPL.docc/ReferenceManual/Statements.md b/TSPL.docc/ReferenceManual/Statements.md index 779dc2a72..5dec797f7 100644 --- a/TSPL.docc/ReferenceManual/Statements.md +++ b/TSPL.docc/ReferenceManual/Statements.md @@ -897,7 +897,7 @@ or the boxed protocol type `any Error`. If the `do` statement doesn't specify the type of error it throws, that type is implied to be `any Error`. For more information about working with errors that have explicit types, -see . +see . If any statement in the `do` code block throws an error, program control is transferred diff --git a/TSPL.docc/ReferenceManual/Types.md b/TSPL.docc/ReferenceManual/Types.md index 03235daac..a618b2bcc 100644 --- a/TSPL.docc/ReferenceManual/Types.md +++ b/TSPL.docc/ReferenceManual/Types.md @@ -350,6 +350,14 @@ 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. Throwing and rethrowing functions are described in From 7e7658833fbe49e2750ff3a0df69786fb60b4fa9 Mon Sep 17 00:00:00 2001 From: Alex Martini Date: Fri, 8 Mar 2024 15:09:28 -0800 Subject: [PATCH 05/20] Expand outline of the new section in the guide --- TSPL.docc/LanguageGuide/ErrorHandling.md | 61 ++++++++++++++---------- 1 file changed, 36 insertions(+), 25 deletions(-) diff --git a/TSPL.docc/LanguageGuide/ErrorHandling.md b/TSPL.docc/LanguageGuide/ErrorHandling.md index a5ebc96f9..fa16d44bd 100644 --- a/TSPL.docc/LanguageGuide/ErrorHandling.md +++ b/TSPL.docc/LanguageGuide/ErrorHandling.md @@ -708,42 +708,53 @@ XXX OUTLINE XXX However, you can write `throws(SomeErrorType)` to throw errors of a specific concrete type. -- What is a boxed protocol (aka existential) error type? - -- What is a concrete error type? - - Because you write a type after `throws` this syntax is also called "typed throws". +- A boxed protocol (aka existential) type matches the reality of most code. + At compile time, you don't know every possible way things could go wrong. + In particular, some errors will come from calling other throwing functions. + +- In contrast, a concrete error type is useful: + + * When the code that throws errors + and the code that handles those errors + are all part of the same larger unit, + like a package or module or library. + These errors are an implementation detail + that users of the library don't see or recover from. + + * In code that only rethrows errors, + especially when the throwing code comes from a closure the caller provided. + Example: `map` in the stdlib. + Xref to reference section -- this chapter doesn't discuss rethrows + + * In an environment where runtime memory allocation isn't possible, + like a constrained embedded system. + Throwing an instance of `any Error` requires allocation. + The type isn't known at compile time -- allocation happens at run time. + (Aside: The performance cost of allocation is small.) + + * In code that has no dependencies, and only ever throws its own errors. + TR: Does this include not depending on the stdlib? + - You can also use opaque types like `throws(some MyErrorProtocol)` -- this is still "concrete" in sense that the errors are all instances of the concrete type that's hidden behind the opaque type. -- When should I use concrete error types? - (See SE-0413 for a list.) - Why shouldn't I just use this everywhere? - -- How do I write a concrete error type for a `do` block? - -- How do I write a concrete error type for a throwing function? - -- What's the type-system relationship - between plain `throws` and `throws(SomeErrorType)`? - When can I interchange them? +- To specify the error type for a `do` block or a throwing function, + write `throws(E)` where `E` is an error type. + For example -- insert `summarize` from RUNNING EXAMPLE below. - If a function or `do` block throws only errors of a single type, the compiler infers that as the concrete error type. - You can explicitly write `throws(any Error` to suppress that. + You can explicitly write `throws(any Error)` to suppress that. -- How do I exhaustively handle errors of a concrete type in a `catch` block? - - +- For a normal error (of boxed protocol type) + the `catch` clause needs to either include a general catch/default + or to propagate the errors it doesn't handle. + For a typed error, the catch clause can be exhaustive. XXX RUNNING EXAMPLE XXX @@ -770,7 +781,7 @@ func summarize(_ ratings: [Int]) throws(StatisticsError) { } func printSummary(_ ratings: [Int]) { - do { + do throws(StatisticsError) { try summarize(ratings) } catch { switch error { From 29cadb2eec8263f9bd202cd8a81baf9ba8721d24 Mon Sep 17 00:00:00 2001 From: Alex Martini Date: Wed, 13 Mar 2024 11:43:17 -0700 Subject: [PATCH 06/20] List 'throws' variant spellings --- TSPL.docc/ReferenceManual/Declarations.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/TSPL.docc/ReferenceManual/Declarations.md b/TSPL.docc/ReferenceManual/Declarations.md index 58a56edd7..e6a9f9c29 100644 --- a/TSPL.docc/ReferenceManual/Declarations.md +++ b/TSPL.docc/ReferenceManual/Declarations.md @@ -1469,6 +1469,10 @@ Calls to a throwing function or method must be wrapped in a `try` or `try!` expr (that is, in the scope of a `try` or `try!` operator). From 16a41358aa7ef2dacfd97afca27dca89e7833809 Mon Sep 17 00:00:00 2001 From: Alex Martini Date: Mon, 25 Mar 2024 16:57:40 -0700 Subject: [PATCH 07/20] Describe subtyping rules for throwing functions --- TSPL.docc/ReferenceManual/Declarations.md | 26 +++++++++++++++-------- TSPL.docc/ReferenceManual/Types.md | 21 ++++++++++-------- 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/TSPL.docc/ReferenceManual/Declarations.md b/TSPL.docc/ReferenceManual/Declarations.md index e6a9f9c29..723ea12e8 100644 --- a/TSPL.docc/ReferenceManual/Declarations.md +++ b/TSPL.docc/ReferenceManual/Declarations.md @@ -1465,25 +1465,33 @@ func <#function name#>(<#parameters#>) throws -> <#return type#> { } ``` +A function that declares what error type it throws +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. - +Writing `throws` without specifying the error type that the function throws +is the same as writing `throws(any Error)`. +Writing `throws(Never)` is the same as omitting throws; +it declares a nonthrowing function. + +A functions type includes whether it throws, +and what type of error it throws. As a result, you can use a nonthrowing function in a context where a throwing one is expected. +For more information, see . You can't overload a function based only on whether the function can throw an error. That said, diff --git a/TSPL.docc/ReferenceManual/Types.md b/TSPL.docc/ReferenceManual/Types.md index a618b2bcc..f6fc61af3 100644 --- a/TSPL.docc/ReferenceManual/Types.md +++ b/TSPL.docc/ReferenceManual/Types.md @@ -350,15 +350,18 @@ 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. +A function that throws a concrete error type +includes the error type that it throws in its function type. +If that error type is a subtype, +then functions that throw the subtype as an error +are subtypes of functions that throw the supertype as an error. + +As a result, 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, +and you can use a function that throws a more specific concrete error type +in the same places as a function that throws a more general error type. Throwing and rethrowing functions are described in and . From d724f484266928b72ac16bb39c8aa4ff6dfddcbd Mon Sep 17 00:00:00 2001 From: Alex Martini Date: Wed, 27 Mar 2024 11:38:53 -0700 Subject: [PATCH 08/20] Frame function subtypes in terms of error subtypes --- TSPL.docc/ReferenceManual/Declarations.md | 5 --- TSPL.docc/ReferenceManual/Types.md | 43 +++++++++++++++-------- 2 files changed, 28 insertions(+), 20 deletions(-) diff --git a/TSPL.docc/ReferenceManual/Declarations.md b/TSPL.docc/ReferenceManual/Declarations.md index 723ea12e8..b1e01d08b 100644 --- a/TSPL.docc/ReferenceManual/Declarations.md +++ b/TSPL.docc/ReferenceManual/Declarations.md @@ -1482,11 +1482,6 @@ Rule for inferring the thrown error type Xref to the guide --> -Writing `throws` without specifying the error type that the function throws -is the same as writing `throws(any Error)`. -Writing `throws(Never)` is the same as omitting throws; -it declares a nonthrowing function. - A functions type includes whether it throws, and what type of error it throws. As a result, you can use a nonthrowing function diff --git a/TSPL.docc/ReferenceManual/Types.md b/TSPL.docc/ReferenceManual/Types.md index f6fc61af3..06e94ff5d 100644 --- a/TSPL.docc/ReferenceManual/Types.md +++ b/TSPL.docc/ReferenceManual/Types.md @@ -347,21 +347,34 @@ 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. -A function that throws a concrete error type -includes the error type that it throws in its function type. -If that error type is a subtype, -then functions that throw the subtype as an error -are subtypes of functions that throw the supertype as an error. - -As a result, 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, -and you can use a function that throws a more specific concrete error type -in the same places as a function that throws a more general error type. +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. +Writing `throws` without specifying an type +is the same as writing `throws(any Error)`. +Omitting `throws` is the same as writing `throws(Never)`. + +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. + Throwing and rethrowing functions are described in and . From caf42a54cad5125b7f99fbc8166a81e5b0962379 Mon Sep 17 00:00:00 2001 From: Alex Martini Date: Wed, 27 Mar 2024 11:52:48 -0700 Subject: [PATCH 09/20] Fix cross reference and wording --- TSPL.docc/ReferenceManual/Declarations.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/TSPL.docc/ReferenceManual/Declarations.md b/TSPL.docc/ReferenceManual/Declarations.md index b1e01d08b..1725547b8 100644 --- a/TSPL.docc/ReferenceManual/Declarations.md +++ b/TSPL.docc/ReferenceManual/Declarations.md @@ -1465,8 +1465,7 @@ func <#function name#>(<#parameters#>) throws -> <#return type#> { } ``` -A function that declares what error type it throws -has the following form: +A function that throws a specific error type has the following form: ```swift func <#function name#>(<#parameters#>) throws(<#error type#>) -> <#return type#> { @@ -1482,11 +1481,11 @@ Rule for inferring the thrown error type Xref to the guide --> -A functions type includes whether it throws, +A function's type includes whether it can throw an error, and what type of error it throws. -As a result, you can use a nonthrowing function +This means, for example, you can use a nonthrowing function in a context where a throwing one is expected. -For more information, see . +For more information, see . You can't overload a function based only on whether the function can throw an error. That said, From b99d8f472bba8673de3dd837471b6eb4483b4c29 Mon Sep 17 00:00:00 2001 From: Alex Martini Date: Thu, 28 Mar 2024 09:34:44 -0700 Subject: [PATCH 10/20] Add the inference rules for throw error types --- TSPL.docc/ReferenceManual/Expressions.md | 12 ++++++------ TSPL.docc/ReferenceManual/Statements.md | 24 +++++++++++++++++++++--- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/TSPL.docc/ReferenceManual/Expressions.md b/TSPL.docc/ReferenceManual/Expressions.md index 1b794f5ec..446619ef4 100644 --- a/TSPL.docc/ReferenceManual/Expressions.md +++ b/TSPL.docc/ReferenceManual/Expressions.md @@ -922,13 +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 only errors of 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 diff --git a/TSPL.docc/ReferenceManual/Statements.md b/TSPL.docc/ReferenceManual/Statements.md index 5dec797f7..d61c5154b 100644 --- a/TSPL.docc/ReferenceManual/Statements.md +++ b/TSPL.docc/ReferenceManual/Statements.md @@ -889,13 +889,31 @@ do throws(<#type#>) { } ``` + + If the `do` statement includes a `throws` clause, -the `do` block can throw only errors of the specified type. -The *type* must be a concrete type that conforms to the `Error` protocol, +the `do` block can throw errors of only the specified *type*. +The *type* must be either +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, -that type is implied to be `any Error`. +the error type is inferred as follows: + +- If every `throws` statement and `try` expression in the `do` code block + is nested inside of an exhaustive error handling mechanism, + then the `do` statement is inferred as nonthrowing. + +- If the `do` code block contains code that throws + errors of only a single type + outside of exhaustive error handling, + then the `do` statement is inferred as throwing 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 the `do` statement is inferred as throwing `any Error`. + For more information about working with errors that have explicit types, see . From ac8a86d053d68c6ba146833e5e2b37bc176f0c93 Mon Sep 17 00:00:00 2001 From: Alex Martini Date: Thu, 28 Mar 2024 09:46:24 -0700 Subject: [PATCH 11/20] Add cross reference Nothing to add about inferring the error type thrown by a function, because that behavior applies only to closures and do-catch blocks. --- TSPL.docc/ReferenceManual/Declarations.md | 10 ++++------ TSPL.docc/ReferenceManual/Types.md | 1 + 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/TSPL.docc/ReferenceManual/Declarations.md b/TSPL.docc/ReferenceManual/Declarations.md index 1725547b8..2156809da 100644 --- a/TSPL.docc/ReferenceManual/Declarations.md +++ b/TSPL.docc/ReferenceManual/Declarations.md @@ -1476,16 +1476,14 @@ func <#function name#>(<#parameters#>) throws(<#error type#>) -> <#return type#> 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). - - A function's type includes whether it can throw an error, and what type of error it throws. This means, for example, you can use a nonthrowing function in a context where a throwing one is expected. -For more information, see . +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, diff --git a/TSPL.docc/ReferenceManual/Types.md b/TSPL.docc/ReferenceManual/Types.md index 06e94ff5d..17efe4e07 100644 --- a/TSPL.docc/ReferenceManual/Types.md +++ b/TSPL.docc/ReferenceManual/Types.md @@ -351,6 +351,7 @@ 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 an type is the same as writing `throws(any Error)`. Omitting `throws` is the same as writing `throws(Never)`. From 3c2816a79d611b870c60bccddd18f9c1f0cc947b Mon Sep 17 00:00:00 2001 From: Alex Martini Date: Thu, 28 Mar 2024 09:50:48 -0700 Subject: [PATCH 12/20] Add inference rule for generics/associated types --- TSPL.docc/ReferenceManual/Types.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/TSPL.docc/ReferenceManual/Types.md b/TSPL.docc/ReferenceManual/Types.md index 17efe4e07..43fcf6bb3 100644 --- a/TSPL.docc/ReferenceManual/Types.md +++ b/TSPL.docc/ReferenceManual/Types.md @@ -376,6 +376,11 @@ As a result of these subtype relationships: - 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 . From 3ea9edd00dbe0ea3a7bf1c33e912b0b14896efc7 Mon Sep 17 00:00:00 2001 From: Alex Martini Date: Wed, 10 Apr 2024 15:46:32 -0700 Subject: [PATCH 13/20] Incorporate feedback from pitch See also https://forums.swift.org/t/70526 --- TSPL.docc/LanguageGuide/ErrorHandling.md | 10 ++++++++-- TSPL.docc/ReferenceManual/Declarations.md | 6 ++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/TSPL.docc/LanguageGuide/ErrorHandling.md b/TSPL.docc/LanguageGuide/ErrorHandling.md index fa16d44bd..b64d505cd 100644 --- a/TSPL.docc/LanguageGuide/ErrorHandling.md +++ b/TSPL.docc/LanguageGuide/ErrorHandling.md @@ -714,8 +714,9 @@ XXX OUTLINE XXX - A boxed protocol (aka existential) type matches the reality of most code. At compile time, you don't know every possible way things could go wrong. In particular, some errors will come from calling other throwing functions. + This is why most code uses a boxed protocol type as its error type. -- In contrast, a concrete error type is useful: +- In contrast, a concrete error type is useful in some special circumstances: * When the code that throws errors and the code that handles those errors @@ -726,6 +727,7 @@ XXX OUTLINE XXX * In code that only rethrows errors, especially when the throwing code comes from a closure the caller provided. + (However, neither rethrows nor typed throws is a strict superset of the other.) Example: `map` in the stdlib. Xref to reference section -- this chapter doesn't discuss rethrows @@ -742,6 +744,7 @@ XXX OUTLINE XXX this is still "concrete" in sense that the errors are all instances of the concrete type that's hidden behind the opaque type. + And there's still one specific error type. - To specify the error type for a `do` block or a throwing function, write `throws(E)` where `E` is an error type. @@ -753,8 +756,11 @@ XXX OUTLINE XXX - For a normal error (of boxed protocol type) the `catch` clause needs to either include a general catch/default + that handles errors whose types the other clauses don't handle, or to propagate the errors it doesn't handle. - For a typed error, the catch clause can be exhaustive. + For a typed error, the catch clause can be exhaustive + without a default clause + by handling just that specific error type. XXX RUNNING EXAMPLE XXX diff --git a/TSPL.docc/ReferenceManual/Declarations.md b/TSPL.docc/ReferenceManual/Declarations.md index 2156809da..666e1431c 100644 --- a/TSPL.docc/ReferenceManual/Declarations.md +++ b/TSPL.docc/ReferenceManual/Declarations.md @@ -1594,6 +1594,12 @@ 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. + + ### Asynchronous Functions and Methods Functions and methods that run asynchronously must be marked with the `async` keyword. From a029497eb4bf227ef8d11dcaf31c8bbfbea193aa Mon Sep 17 00:00:00 2001 From: Alex Martini Date: Thu, 18 Apr 2024 11:03:11 -0700 Subject: [PATCH 14/20] Start expanding the outline --- TSPL.docc/LanguageGuide/ErrorHandling.md | 163 ++++++++++++++--------- 1 file changed, 103 insertions(+), 60 deletions(-) diff --git a/TSPL.docc/LanguageGuide/ErrorHandling.md b/TSPL.docc/LanguageGuide/ErrorHandling.md index b64d505cd..b9376cc6f 100644 --- a/TSPL.docc/LanguageGuide/ErrorHandling.md +++ b/TSPL.docc/LanguageGuide/ErrorHandling.md @@ -699,47 +699,124 @@ let photo = try! loadImage(atPath: "./Resources/John Appleseed.jpg") ``` --> -## Specifying a Concrete Error Type +## 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 by code you didn't write. +New versions of a library can throw new errors --- +including libraries used by your dependencies --- +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 their own specific `catch` clause. + +However, +some code throws error of only a specific type, +so it's useful to specify that type. + +- When you encapsulate errors within some unit of code, + like a library, + if using a specific error type is useful for you. + In this case, + the library always handles all of its own errors. + This kind of error is an implementation detail of the library. + +- When targeting an embedded system + where dynamic allocation of memory isn't possible. + Swift needs to allocate memory at run time + when throwing an instance `any Error` or other boxed protocol types. + +- When you throw errors from code that + doesn't depend on any other throwing code, + and throws only its own errors. + + + +- When you rethrow errors, + if you want to preserve the error type. + -XXX OUTLINE XXX +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` +you write `throws(StatisticsError)` when declaring the function. +Because you write a type after `throws` +this syntax is also called *typed throws*. + +```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("One star:", counts[1]!) + print("Two stars:", counts[2]!) + print("Three stars:", counts[3]!) + + print("*", counts[1]!, "-- **", counts[2]!, "-- ***", counts[3]!) +} +``` + + + +When you write a specific error type, +Swift checks at compile time that you don't throw any other errors. + + +Code that throws a single specific error type +doesn't include the default `catch` clause --- +instead, Swift verifies that every possible error value +has a corresponding `catch` clause. + +```swift +func printSummary(_ ratings: [Int]) { + do throws(StatisticsError) { + try summarize(ratings) + } catch { + switch error { + case .noRatings: + print("No ratings available") + case .invalidRating(let rating): + print("Invalid rating: \(rating)") + } + } +} +``` + +## XXX OUTLINE XXX - Most code that throws errors just writes `throws`. This is the same as writing `throws(any Error)`. However, you can write `throws(SomeErrorType)` to throw errors of a specific concrete type. -- Because you write a type after `throws` - this syntax is also called "typed throws". - -- A boxed protocol (aka existential) type matches the reality of most code. - At compile time, you don't know every possible way things could go wrong. - In particular, some errors will come from calling other throwing functions. - This is why most code uses a boxed protocol type as its error type. - - In contrast, a concrete error type is useful in some special circumstances: - * When the code that throws errors - and the code that handles those errors - are all part of the same larger unit, - like a package or module or library. - These errors are an implementation detail - that users of the library don't see or recover from. - * In code that only rethrows errors, especially when the throwing code comes from a closure the caller provided. (However, neither rethrows nor typed throws is a strict superset of the other.) Example: `map` in the stdlib. Xref to reference section -- this chapter doesn't discuss rethrows - * In an environment where runtime memory allocation isn't possible, - like a constrained embedded system. - Throwing an instance of `any Error` requires allocation. - The type isn't known at compile time -- allocation happens at run time. - (Aside: The performance cost of allocation is small.) - - * In code that has no dependencies, and only ever throws its own errors. - TR: Does this include not depending on the stdlib? - - You can also use opaque types like `throws(some MyErrorProtocol)` -- this is still "concrete" in sense that the errors are all instances of the concrete type @@ -765,40 +842,6 @@ XXX OUTLINE XXX XXX RUNNING EXAMPLE XXX ```swift -enum StatisticsError: Error { - case noRatings - case invalidRating(Int) -} - -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("One star:", counts[1]!) - print("Two stars:", counts[2]!) - print("Three stars:", counts[3]!) - - print("*", counts[1]!, "-- **", counts[2]!, "-- ***", counts[3]!) -} - -func printSummary(_ ratings: [Int]) { - do throws(StatisticsError) { - try summarize(ratings) - } catch { - switch error { - case .noRatings: - print("No ratings available") - case .invalidRating(let rating): - print("Invalid rating: \(rating)") - } - } -} - printSummary([1, 2, 3, 2, 2, 1]) printSummary([]) printSummary([1, 100]) From 0812c6e7d97fca1c3c9f8a93007ca08dab3a663a Mon Sep 17 00:00:00 2001 From: Alex Martini Date: Fri, 19 Apr 2024 23:58:19 -0700 Subject: [PATCH 15/20] Continue expanding the outline --- TSPL.docc/LanguageGuide/ErrorHandling.md | 158 ++++++++++++++--------- 1 file changed, 96 insertions(+), 62 deletions(-) diff --git a/TSPL.docc/LanguageGuide/ErrorHandling.md b/TSPL.docc/LanguageGuide/ErrorHandling.md index b9376cc6f..5dd769ffd 100644 --- a/TSPL.docc/LanguageGuide/ErrorHandling.md +++ b/TSPL.docc/LanguageGuide/ErrorHandling.md @@ -707,7 +707,8 @@ 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 by code you didn't write. +especially when propagating errors thrown somewhere else. +This approach also reflects the fact that errors can change over time. New versions of a library can throw new errors --- including libraries used by your dependencies --- and the rich complexity of real-world user configurations @@ -716,21 +717,48 @@ The error handling code in the examples above always includes a default case to handle errors that don't have their own specific `catch` clause. -However, -some code throws error of only a specific type, -so it's useful to specify that type. +In some special cases, +you might write code that's specific about what error it throws: -- When you encapsulate errors within some unit of code, - like a library, - if using a specific error type is useful for you. - In this case, - the library always handles all of its own errors. - This kind of error is an implementation detail of the library. - -- When targeting an embedded system +- When running code on an embedded system where dynamic allocation of memory isn't possible. - Swift needs to allocate memory at run time - when throwing an instance `any Error` or other boxed protocol types. + Throwing an instance `any Error` or another boxed protocol type + requires allocating memory at run time to store the error. + Throwing an error of a specific type instead + lets Swift allocate that memory upfront. + +- When the errors are an implementation detail of a library, + or some other unit of code, + and aren't part of the interface to that code. + Because only the library's code throws errors --- + it doesn't propagate errors from other code --- + you can make an exhaustive list of all possible failures. + And because the library always handles its own errors, + + + Because all of these errors are thrown within the library's code, + and they're always handled within that library. + Because + Because the library's clients never see the errors, + ◊ the API surface expressively limitations don't matter + +- + * In code that only rethrows errors, + especially when the throwing code comes from a closure the caller provided. + (However, neither rethrows nor typed throws is a strict superset of the other.) + Example: `map` in the stdlib. + Xref to reference section -- this chapter doesn't discuss rethrows + + +In most code, +you don't specify the type for the errors it throws, +implicitly throwing an error of type `any Error` +However, +Swift also supports +some code only throws errors of a specific type. +in some places your code only throws +you might need to write code that throws errors of only a specific type, +so it's useful to specify that type. - When you throw errors from code that doesn't depend on any other throwing code, @@ -754,9 +782,10 @@ enum StatisticsError: Error { ``` To specify that a function throws only `StatisticsError` -you write `throws(StatisticsError)` when declaring the function. -Because you write a type after `throws` -this syntax is also called *typed throws*. +you write `throws(StatisticsError)` when declaring the function, +instead of just writing `throws`. +This syntax is also called *typed throws* +because you write the error type after `throws` --- for example: ```swift func summarize(_ ratings: [Int]) throws(StatisticsError) { @@ -768,54 +797,75 @@ func summarize(_ ratings: [Int]) throws(StatisticsError) { counts[rating]! += 1 } - print("One star:", counts[1]!) - print("Two stars:", counts[2]!) - print("Three stars:", counts[3]!) - 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. + +You can use the shorthand notation `throw .noRatings` +instead of writing `throw StatisticsError.noRatings` +because the function's error type is already defined. + + +◊ throws(StatisticsError) is a subtype of throws(any Error) +◊ so you can write `try summarize(...)` in a plain `throwing` context too +- Most code that throws errors just writes `throws`. + This is the same as writing `throws(any Error)`. + However, you can write `throws(SomeErrorType)` + to throw errors of a specific concrete type. + -When you write a specific error type, -Swift checks at compile time that you don't throw any other errors. +When you write a specific error type, +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. +◊ rewrite Code that throws a single specific error type -doesn't include the default `catch` clause --- +doesn't need to include the default `catch` clause --- instead, Swift verifies that every possible error value has a corresponding `catch` clause. + + + +In addition to specifying the error type for a function, +you can also write a specific error type +in a `do`-`catch` block. +For example: + ```swift -func printSummary(_ ratings: [Int]) { - do throws(StatisticsError) { - try summarize(ratings) - } catch { - switch error { - case .noRatings: - print("No ratings available") - case .invalidRating(let rating): - print("Invalid rating: \(rating)") - } +let ratings = [1, 2, 3, 2, 2, 1] +do throws(StatisticsError) { + try summarize(ratings) +} catch { + switch error { + case .noRatings: + print("No ratings available") + case .invalidRating(let rating): + print("Invalid rating: \(rating)") } } +// Prints XXX ``` -## XXX OUTLINE XXX +In this code, XXX -- Most code that throws errors just writes `throws`. - This is the same as writing `throws(any Error)`. - However, you can write `throws(SomeErrorType)` - to throw errors of a specific concrete type. -- In contrast, a concrete error type is useful in some special circumstances: +XXX +If a function or `do` block throws only errors of a single type, +the compiler infers that as the concrete error type. +You can explicitly write `throws(any Error)` to suppress that. - * In code that only rethrows errors, - especially when the throwing code comes from a closure the caller provided. - (However, neither rethrows nor typed throws is a strict superset of the other.) - Example: `map` in the stdlib. - Xref to reference section -- this chapter doesn't discuss rethrows + +## XXX OUTLINE XXX - You can also use opaque types like `throws(some MyErrorProtocol)` -- this is still "concrete" in sense that @@ -823,14 +873,6 @@ func printSummary(_ ratings: [Int]) { that's hidden behind the opaque type. And there's still one specific error type. -- To specify the error type for a `do` block or a throwing function, - write `throws(E)` where `E` is an error type. - For example -- insert `summarize` from RUNNING EXAMPLE below. - -- If a function or `do` block throws only errors of a single type, - the compiler infers that as the concrete error type. - You can explicitly write `throws(any Error)` to suppress that. - - For a normal error (of boxed protocol type) the `catch` clause needs to either include a general catch/default that handles errors whose types the other clauses don't handle, @@ -839,14 +881,6 @@ func printSummary(_ ratings: [Int]) { without a default clause by handling just that specific error type. -XXX RUNNING EXAMPLE XXX - -```swift -printSummary([1, 2, 3, 2, 2, 1]) -printSummary([]) -printSummary([1, 100]) -``` - ## Specifying Cleanup Actions You use a `defer` statement to execute a set of statements From 356f018b6f89d1a3c12c184107b5b43eb57435c3 Mon Sep 17 00:00:00 2001 From: Alex Martini Date: Sat, 20 Apr 2024 17:34:40 -0700 Subject: [PATCH 16/20] Expand the rest of the outline --- TSPL.docc/LanguageGuide/ErrorHandling.md | 188 ++++++++++++---------- TSPL.docc/ReferenceManual/Declarations.md | 2 + TSPL.docc/ReferenceManual/Types.md | 3 + 3 files changed, 105 insertions(+), 88 deletions(-) diff --git a/TSPL.docc/LanguageGuide/ErrorHandling.md b/TSPL.docc/LanguageGuide/ErrorHandling.md index 5dd769ffd..9e22116aa 100644 --- a/TSPL.docc/LanguageGuide/ErrorHandling.md +++ b/TSPL.docc/LanguageGuide/ErrorHandling.md @@ -717,58 +717,33 @@ The error handling code in the examples above always includes a default case to handle errors that don't have their own specific `catch` clause. -In some special cases, -you might write code that's specific about what error it throws: +Most Swift code doesn't specify the type for the errors it throws. +However, +in some special cases, +you might limit code to throwing errors of only one specific type: - When running code on an embedded system - where dynamic allocation of memory isn't possible. + that doesn't support dynamic allocation of memory. Throwing an instance `any Error` or another boxed protocol type - requires allocating memory at run time to store the error. - Throwing an error of a specific type instead - lets Swift allocate that memory upfront. + requires allocating memory at runtime to store the error. + Throwing an error of a specific type + lets Swift allocate that memory upfront instead. -- When the errors are an implementation detail of a library, - or some other unit of code, +- When the errors are used only within some unit of code, + like a library, and aren't part of the interface to that code. - Because only the library's code throws errors --- - it doesn't propagate errors from other code --- + Because the errors come only from the library, + and not from other dependencies or the library's clients, you can make an exhaustive list of all possible failures. - And because the library always handles its own errors, - - - Because all of these errors are thrown within the library's code, + And because these errors are an implementation detail of the library, and they're always handled within that library. - Because - Because the library's clients never see the errors, - ◊ the API surface expressively limitations don't matter - -- - * In code that only rethrows errors, - especially when the throwing code comes from a closure the caller provided. - (However, neither rethrows nor typed throws is a strict superset of the other.) - Example: `map` in the stdlib. - Xref to reference section -- this chapter doesn't discuss rethrows - -In most code, -you don't specify the type for the errors it throws, -implicitly throwing an error of type `any Error` -However, -Swift also supports -some code only throws errors of a specific type. -in some places your code only throws -you might need to write code that throws errors of only a specific type, -so it's useful to specify that type. - -- When you throw errors from code that - doesn't depend on any other throwing code, - and throws only its own errors. - - - -- When you rethrow errors, - if you want to preserve the error type. - +- In code that only throws errors that were thrown elsewhere, + like a function that takes a closure argument + and propagates any errors from that closure. + For a comparison between `rethrows` + and throwing a specific, generic, error type + see . For example, consider code that summarizes ratings @@ -781,7 +756,7 @@ enum StatisticsError: Error { } ``` -To specify that a function throws only `StatisticsError` +To specify that a function throws only `StatisticsError` values as its errors you write `throws(StatisticsError)` when declaring the function, instead of just writing `throws`. This syntax is also called *typed throws* @@ -805,44 +780,54 @@ 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. - -You can use the shorthand notation `throw .noRatings` -instead of writing `throw StatisticsError.noRatings` +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 like `throw .noRatings` +instead of writing `throw StatisticsError.noRatings` +when throwing an error in a function like this. - -◊ throws(StatisticsError) is a subtype of throws(any Error) -◊ so you can write `try summarize(...)` in a plain `throwing` context too -- Most code that throws errors just writes `throws`. - This is the same as writing `throws(any Error)`. - However, you can write `throws(SomeErrorType)` - to throw errors of a specific concrete type. - - - -When you write a specific error type, +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. -◊ rewrite -Code that throws a single specific error type -doesn't need to include the default `catch` clause --- -instead, Swift verifies that every possible error value -has a corresponding `catch` clause. +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. + -In addition to specifying the error type for a function, -you can also write a specific error type -in a `do`-`catch` block. +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 = [1, 2, 3, 2, 2, 1] +let ratings = [] do throws(StatisticsError) { try summarize(ratings) } catch { @@ -853,33 +838,60 @@ do throws(StatisticsError) { print("Invalid rating: \(rating)") } } -// Prints XXX +// Prints "No ratings available" ``` -In this code, XXX - +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 it can propagate unhandled errors for some surrounding scope to handle. +Here, it handles all of the errors, +using a switch 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`. -XXX -If a function or `do` block throws only errors of a single type, -the compiler infers that as the concrete error type. -You can explicitly write `throws(any Error)` to suppress that. + +The `catch` clause above uses a switch +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 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. -## XXX OUTLINE XXX +If a function or `do` block throws only errors of 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: -- You can also use opaque types like `throws(some MyErrorProtocol)` -- - this is still "concrete" in sense that - the errors are all instances of the concrete type - that's hidden behind the opaque type. - And there's still one specific error type. +```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" +``` -- For a normal error (of boxed protocol type) - the `catch` clause needs to either include a general catch/default - that handles errors whose types the other clauses don't handle, - or to propagate the errors it doesn't handle. - For a typed error, the catch clause can be exhaustive - without a default clause - by handling just that specific error type. +Even though the `do`-`catch` block above +doesn't specify what type of error it throws, +it's still understood as throwing `StatisticsError`. +You can explicitly write `throws(any Error)` +to avoid letting Swift infer typed throws. ## Specifying Cleanup Actions diff --git a/TSPL.docc/ReferenceManual/Declarations.md b/TSPL.docc/ReferenceManual/Declarations.md index 666e1431c..d632c46f2 100644 --- a/TSPL.docc/ReferenceManual/Declarations.md +++ b/TSPL.docc/ReferenceManual/Declarations.md @@ -1598,6 +1598,8 @@ and a rethrowing method can satisfy a protocol requirement for a throwing method func f(closure: () throws(E) -> Int) throws(E) -> Int { ... } func g(closure: () throws -> Int) rethrows -> Int { ... } + +map() from the stdlib is another example --> ### Asynchronous Functions and Methods diff --git a/TSPL.docc/ReferenceManual/Types.md b/TSPL.docc/ReferenceManual/Types.md index 43fcf6bb3..8d39ce506 100644 --- a/TSPL.docc/ReferenceManual/Types.md +++ b/TSPL.docc/ReferenceManual/Types.md @@ -355,6 +355,9 @@ The throw error type must conform to the `Error` protocol. Writing `throws` without specifying an 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 From 4baad404a64477c7c518924c650649dc6b857f8b Mon Sep 17 00:00:00 2001 From: Alex Martini Date: Wed, 15 May 2024 16:03:50 -0700 Subject: [PATCH 17/20] Start incorporating editorial feedback Edits from Co-authored-by: Rebecca Teibloom --- TSPL.docc/LanguageGuide/ErrorHandling.md | 54 ++++++++++++----------- TSPL.docc/ReferenceManual/Declarations.md | 2 +- TSPL.docc/ReferenceManual/Expressions.md | 4 +- TSPL.docc/ReferenceManual/Statements.md | 4 +- TSPL.docc/ReferenceManual/Types.md | 8 ++-- 5 files changed, 37 insertions(+), 35 deletions(-) diff --git a/TSPL.docc/LanguageGuide/ErrorHandling.md b/TSPL.docc/LanguageGuide/ErrorHandling.md index 9e22116aa..47c4b69e6 100644 --- a/TSPL.docc/LanguageGuide/ErrorHandling.md +++ b/TSPL.docc/LanguageGuide/ErrorHandling.md @@ -709,40 +709,42 @@ you don't know ahead of time every error that could happen while the code is running, especially when propagating errors thrown somewhere else. This approach also reflects the fact that errors can change over time. -New versions of a library can throw new errors --- -including libraries used by your dependencies --- +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 their own specific `catch` clause. +that don't have a specific `catch` clause. Most Swift code doesn't specify the type for the errors it throws. However, -in some special cases, -you might limit code to throwing errors of only one specific type: +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 `any Error` or another boxed protocol type + Throwing an instance of `any Error` or another boxed protocol type requires allocating memory at runtime to store the error. - Throwing an error of a specific type - lets Swift allocate that memory upfront instead. + In contrast, + throwing an error of a specific type + lets Swift allocate that memory upfront. -- When the errors are used only within some unit of code, +- 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 only from the library, + 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, - and they're always handled within that library. + they're always handled within that library. -- In code that only throws errors that were thrown elsewhere, +- In code that throws only errors that were thrown elsewhere, like a function that takes a closure argument and propagates any errors from that closure. - For a comparison between `rethrows` - and throwing a specific, generic, error type + For a comparison between propagating a specific error type + and using `rethrows`, see . For example, @@ -756,9 +758,9 @@ enum StatisticsError: Error { } ``` -To specify that a function throws only `StatisticsError` values as its errors -you write `throws(StatisticsError)` when declaring the function, -instead of just writing `throws`. +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` --- for example: @@ -783,7 +785,7 @@ 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 like `throw .noRatings` +You can use the short form, `throw .noRatings`, instead of writing `throw StatisticsError.noRatings` when throwing an error in a function like this. @@ -845,10 +847,10 @@ 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 it can propagate unhandled errors for some surrounding scope to handle. -Here, it handles all of the errors, -using a switch with one case for each enumeration value. +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`. @@ -857,17 +859,17 @@ Because the `do`-`catch` statement throws `StatisticsError` values, -The `catch` clause above uses a switch +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 wouldn't be exhaustive anymore. +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 only errors of a single type, +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: @@ -889,7 +891,7 @@ do { Even though the `do`-`catch` block above doesn't specify what type of error it throws, -it's still understood as throwing `StatisticsError`. +Swift infers that it throws `StatisticsError`. You can explicitly write `throws(any Error)` to avoid letting Swift infer typed throws. diff --git a/TSPL.docc/ReferenceManual/Declarations.md b/TSPL.docc/ReferenceManual/Declarations.md index d632c46f2..14cc9dd9e 100644 --- a/TSPL.docc/ReferenceManual/Declarations.md +++ b/TSPL.docc/ReferenceManual/Declarations.md @@ -1476,7 +1476,7 @@ func <#function name#>(<#parameters#>) throws(<#error type#>) -> <#return type#> 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). -A function's type includes whether it can throw an error, +A function's type includes whether it can throw an error and what type of error it throws. This means, for example, you can use a nonthrowing function in a context where a throwing one is expected. diff --git a/TSPL.docc/ReferenceManual/Expressions.md b/TSPL.docc/ReferenceManual/Expressions.md index 446619ef4..eac7bc81a 100644 --- a/TSPL.docc/ReferenceManual/Expressions.md +++ b/TSPL.docc/ReferenceManual/Expressions.md @@ -925,9 +925,9 @@ explicitly marks a closure as throwing or asynchronous. 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. -If a throwing closure throws only errors of a single type, +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`. +otherwise, it's understood as throwing `any Error`. Likewise, if the body includes an `await` expression, it's understood to be asynchronous. diff --git a/TSPL.docc/ReferenceManual/Statements.md b/TSPL.docc/ReferenceManual/Statements.md index d61c5154b..a4f135a40 100644 --- a/TSPL.docc/ReferenceManual/Statements.md +++ b/TSPL.docc/ReferenceManual/Statements.md @@ -893,7 +893,7 @@ do throws(<#type#>) { If the `do` statement includes a `throws` clause, the `do` block can throw errors of only the specified *type*. -The *type* must be either +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`. @@ -901,7 +901,7 @@ If the `do` statement doesn't specify the type of error it throws, the error type is inferred as follows: - If every `throws` statement and `try` expression in the `do` code block - is nested inside of an exhaustive error handling mechanism, + is nested inside of an exhaustive error-handling mechanism, then the `do` statement is inferred as nonthrowing. - If the `do` code block contains code that throws diff --git a/TSPL.docc/ReferenceManual/Types.md b/TSPL.docc/ReferenceManual/Types.md index 8d39ce506..b85f43614 100644 --- a/TSPL.docc/ReferenceManual/Types.md +++ b/TSPL.docc/ReferenceManual/Types.md @@ -352,7 +352,7 @@ 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 an type +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 @@ -366,9 +366,9 @@ 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)`. +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: From a0ee3a96e76d67175a45d46a6f70692d7cdea654 Mon Sep 17 00:00:00 2001 From: Alex Martini Date: Thu, 16 May 2024 16:54:54 -0700 Subject: [PATCH 18/20] Iterate on editorial feedback --- TSPL.docc/LanguageGuide/ErrorHandling.md | 6 ++++-- TSPL.docc/ReferenceManual/Declarations.md | 2 +- TSPL.docc/ReferenceManual/Statements.md | 8 ++++---- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/TSPL.docc/LanguageGuide/ErrorHandling.md b/TSPL.docc/LanguageGuide/ErrorHandling.md index 47c4b69e6..5308fee7c 100644 --- a/TSPL.docc/LanguageGuide/ErrorHandling.md +++ b/TSPL.docc/LanguageGuide/ErrorHandling.md @@ -762,7 +762,9 @@ 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` --- for example: +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) { @@ -808,7 +810,7 @@ func someThrowingFunction() -> throws { 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)` --- +You could also write the error type explicitly as `throws(any Error)`; the code below is equivalent to the code above: ```swift diff --git a/TSPL.docc/ReferenceManual/Declarations.md b/TSPL.docc/ReferenceManual/Declarations.md index 14cc9dd9e..b83885293 100644 --- a/TSPL.docc/ReferenceManual/Declarations.md +++ b/TSPL.docc/ReferenceManual/Declarations.md @@ -1478,7 +1478,7 @@ Calls to a throwing function or method must be wrapped in a `try` or `try!` expr A function's type includes whether it can throw an error and what type of error it throws. -This means, for example, you can use a nonthrowing function +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 . diff --git a/TSPL.docc/ReferenceManual/Statements.md b/TSPL.docc/ReferenceManual/Statements.md index a4f135a40..6d2de9c2b 100644 --- a/TSPL.docc/ReferenceManual/Statements.md +++ b/TSPL.docc/ReferenceManual/Statements.md @@ -898,21 +898,21 @@ 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, -the error type is inferred as follows: +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 the `do` statement is inferred as nonthrowing. + 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, - then the `do` statement is inferred as throwing that concrete error type. + 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 the `do` statement is inferred as throwing `any Error`. + then Swift infers that the `do` statement throws `any Error`. For more information about working with errors that have explicit types, see . From 2fe85ff8474389618fb9924f2b87071d85a26a25 Mon Sep 17 00:00:00 2001 From: Alex Martini Date: Wed, 29 May 2024 17:00:29 -0700 Subject: [PATCH 19/20] Incorporate tech review. Co-authored-by: Doug Gregor --- TSPL.docc/LanguageGuide/ErrorHandling.md | 21 +++++++++++++++------ TSPL.docc/ReferenceManual/Declarations.md | 6 ++++++ TSPL.docc/ReferenceManual/Statements.md | 3 +-- TSPL.docc/ReferenceManual/Types.md | 1 - 4 files changed, 22 insertions(+), 9 deletions(-) diff --git a/TSPL.docc/LanguageGuide/ErrorHandling.md b/TSPL.docc/LanguageGuide/ErrorHandling.md index 5308fee7c..661ca1d02 100644 --- a/TSPL.docc/LanguageGuide/ErrorHandling.md +++ b/TSPL.docc/LanguageGuide/ErrorHandling.md @@ -708,7 +708,7 @@ 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. -This approach also reflects the fact that errors can change over time. +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, @@ -729,7 +729,7 @@ in the following special cases: requires allocating memory at runtime to store the error. In contrast, throwing an error of a specific type - lets Swift allocate that memory upfront. + lets Swift avoid heap allocation for errors. - When the errors are an implementation detail of some unit of code, like a library, @@ -740,7 +740,7 @@ in the following special cases: And because these errors are an implementation detail of the library, they're always handled within that library. -- In code that throws only errors that were thrown elsewhere, +- 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 @@ -824,7 +824,18 @@ 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. @@ -859,8 +870,6 @@ 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` diff --git a/TSPL.docc/ReferenceManual/Declarations.md b/TSPL.docc/ReferenceManual/Declarations.md index b83885293..aef8d36f0 100644 --- a/TSPL.docc/ReferenceManual/Declarations.md +++ b/TSPL.docc/ReferenceManual/Declarations.md @@ -1600,6 +1600,12 @@ func f(closure: () throws(E) -> Int) throws(E) -> Int { ... } func g(closure: () throws -> Int) rethrows -> Int { ... } map() from the stdlib is another example + +XXX FROM DOUG +Part of me wants to suggest that this whole section be revised to replace rethrows entirely with the use of generic typed throws, because that's how I expect the language to go over time. Then, rethrows would become more of a historical anecdote. However, that is a lot of work, and perhaps we'll find that rethrows will remain for longer than I expect. If so, I think it's fine to keep this comparison short: take the someFunction example from the beginning of the section and make it generic over the thrown error type, and note that it's providing a more precise guarantee about what errors it rethrows than rethrows does (because rethrows always produces an any Error). +XXX END FROM DOUG + +Filed rdar://128972373 --> ### Asynchronous Functions and Methods diff --git a/TSPL.docc/ReferenceManual/Statements.md b/TSPL.docc/ReferenceManual/Statements.md index 6d2de9c2b..c796b4321 100644 --- a/TSPL.docc/ReferenceManual/Statements.md +++ b/TSPL.docc/ReferenceManual/Statements.md @@ -889,8 +889,6 @@ do throws(<#type#>) { } ``` - - If the `do` statement includes a `throws` clause, the `do` block can throw errors of only the specified *type*. The *type* must be @@ -907,6 +905,7 @@ Swift infers the error type as follows: - 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 diff --git a/TSPL.docc/ReferenceManual/Types.md b/TSPL.docc/ReferenceManual/Types.md index b85f43614..c22299b07 100644 --- a/TSPL.docc/ReferenceManual/Types.md +++ b/TSPL.docc/ReferenceManual/Types.md @@ -348,7 +348,6 @@ another function that takes and returns an `Int`. Function types for functions 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. From 66287ca48d187f6248e7176f253bd3773e1ed147 Mon Sep 17 00:00:00 2001 From: Alex Martini Date: Thu, 30 May 2024 12:18:10 -0700 Subject: [PATCH 20/20] Add example of throws(E) vs rethrows --- TSPL.docc/ReferenceManual/Declarations.md | 24 +++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/TSPL.docc/ReferenceManual/Declarations.md b/TSPL.docc/ReferenceManual/Declarations.md index aef8d36f0..d0e5bdfb7 100644 --- a/TSPL.docc/ReferenceManual/Declarations.md +++ b/TSPL.docc/ReferenceManual/Declarations.md @@ -1594,18 +1594,26 @@ 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. - ### Asynchronous Functions and Methods