From 8a3ced1fcf9e52ba672205dbc8e7248fc3631b36 Mon Sep 17 00:00:00 2001 From: Geoffrey Romer Date: Thu, 18 Jun 2020 10:59:08 -0700 Subject: [PATCH 01/12] Initial draft of error handling principles. --- docs/project/principles/error_handling.md | 198 ++++++++++++++++++++++ 1 file changed, 198 insertions(+) create mode 100644 docs/project/principles/error_handling.md diff --git a/docs/project/principles/error_handling.md b/docs/project/principles/error_handling.md new file mode 100644 index 0000000000000..8cecc7eccc93e --- /dev/null +++ b/docs/project/principles/error_handling.md @@ -0,0 +1,198 @@ +# Principles: Error handling + + + +## Principles + +### Programming errors are not recoverable + +The Carbon language and standard library will not use recoverable +error-reporting mechanisms to report programming errors, i.e. errors that are +caused by incorrect user code. Furthermore, Carbon's design will not prioritize +use cases involving recovery from programming errors. + +Recovering from an error generally consists of discarding any state that might +be invalidated by the original cause of the error, and then transferring control +to a point that doesn't depend on the discarded state. For example, a function +that reads data from a file and validates a checksum might avoid modifying any +nonlocal state until validation is successful, and return early if validation +fails. This recovery strategy relies on the fact that the likely causes of the +failure are known and bounded (probably a malformed input file or a hardware I/O +error), which allows us to put a bound on the state that might have been +invalidated. + +However, when a programming error is detected, the original cause is neither +known nor bounded. For example, if a function dereferences a dangling pointer, +that might mean that the author of the function forgot to check some condition +before dereferencing, or that the caller incorrectly passed a dangling pointer, +or that some other code released the memory too early, among many other +possibilities. Consequently, the only way to (mostly) reliably recover from a +programming error is to discard the entire address space and terminate the +program. + +Thus, we expect that supporting recovery from programming errors would provide +little or no benefit. Furthermore, it would be harmful to several of Carbon's +primary goals: + +- It would impose a pervasive performance overhead, because recoverable error + handling is never free, and a programming error can occur anywhere. +- Because potential programming errors are pervasive, they would have to + propagate invisibly, which makes code harder to understand (see + [below](http://TODO)). +- It would inhibit evolution of Carbon libraries, and the Carbon language, by + preventing them from changing how they respond to incorrect code. +- Similarly, it would prevent Carbon users from choosing different + performance/safety tradeoffs for handling programming errors: if an + out-of-bounds array access is required to throw an exception, users can't + disable bounds checks, regardless of their risk tolerance, because code might + rely on those exceptions being thrown. + +#### Examples + +If Carbon supports assertions and/or contract checking, failed assertions will +not throw exceptions, even as an optional build mode. + +### Memory exhaustion is not recoverable + +The Carbon standard library will not ordinarily use recoverable error-reporting +mechanisms to report memory exhaustion, or support user-defined code that does. + +Memory exhaustion is not a programming error, and it is feasible to write code +that can successfully recover from it. However, the available evidence indicates +that very little C++ code actually does so correctly (TODO: Cite Herb's paper), +which suggests that very little C++ code actually needs to do so, and we see no +reason to expect Carbon's users to differ in this respect. + +Supporting recovery from memory exhaustion would impose many of the same harms +as supporting recovery from programming errors, and for the same basic reason: +memory allocation is pervasive, and so a mechanism for recovering from it would +have to be similarly pervasive. Furthermore, experience with C++ has shown that +attempting to support memory exhaustion can seriously deform the design of an +API. + +#### Examples + +The `pop` operation on a Carbon queue will return the value removed from the +queue. This is in contrast to C++'s `std::queue::pop()`, which does not return +the value popped from the queue, because it is impossible to do so safely if +copying the popped value can throw an out-of-memory error (TODO: cite GotW about +this). Instead, the user must first examine the front of the queue, and then pop +it as a separate operation. Not only is this awkward for users, it means that +concurrent queues cannot match the API of non-concurrent queues (because +separate `front()` and `pop()` calls would create a race condition). + +#### Caveats + +Carbon will probably provide a low-level way to allocate heap memory that makes +allocation failure recoverable, because doing so appears to have few drawbacks. +However, users may need to build their own libraries on top of it, rather that +relying on the Carbon standard library, if they want to take advantage of it. +There probably will not be a way to recover from _stack_ exhaustion, because +there is no known way of doing that without major drawbacks, and users who can't +tolerate crashing due to stack overflow can normally prevent it via static +analysis. + +### Recoverable errors are explicit in function declarations + +Carbon functions that can emit recoverable errors will always be explicitly +marked in all function declarations. + +The possibility of emitting recoverable errors is nearly as fundamental to a +function's API as its return type, and so Carbon APIs will be substantially +clearer to read, and safer to use, if we require consistent, compiler-checked +documentation of that property. Furthermore, as noted above, the mechanisms for +emitting a recoverable error always impose some performance overhead, so the +compiler must be able to distinguish the functions that need that overhead from +the ones that do not. + +The default should be that functions do not emit errors, because that's the +simpler and more efficient behavior, and we also expect it to be the common +case. + +#### Caveats + +This principle applies only to the mechanisms that Carbon natively provides for +error recovery. We cannot guarantee that errors are explicitly marked if they +are conveyed via some other mechanism (e.g. `errno`-style thread-local storage). + +### Recoverable errors are explicit at the callsite + +Operations that can emit recoverable errors will always be explicitly marked at +the point of use. + +If errors can propagate silently (as with exceptions in most languages), it +creates control flow paths that are not visible to the reader of the code, and +it is extremely difficult to reason about procedural code when you aren't aware +of all control flow paths. This would make Carbon code harder to understand, +maintain, and debug. + +Conversely, if errors can be silently ignored (as with error return codes in +many languages), it creates a major risk of accidentally resuming normal +execution without actually recovering from the error (i.e. discarding +invalidated state). This, too, would make it extremely difficult to reason +correctly about Carbon code. + +Either possibility would also allow code to evolve in unsafe ways. Changing a +function to allow it to emit errors is semantically a breaking change: client +code must now contend with a previously-impossible failure case. Requiring +errors to be marked at the callsite ensures that this breakage manifests at +build time. + +#### Caveats + +We cannot prevent errors from being ignored if they are conveyed via a +nonstandard mechanism (e.g. `errno`). + +### Error propagation must be straightforward + +Carbon will provide a means to propagate recoverable errors from any function +call to the caller of the enclosing function, with minimal textual overhead. + +In our experience, it is very common for C++ code to propagate errors across +multiple layers of the call stack. C++ exceptions support this natively, and +programmers in environments without exceptions usually develop a lightweight way +to propagate errors explicitly, typically via a macro containing a conditional +`return`. In some cases they even resort to using nonstandard language +extensions in order to be able to use this operation within expressions, rather +than only at the statement level. + +Given the ubiquity of this use case, Carbon must provide support for it that can +be used without altering the structure of the code, or making the non-error-case +logic less clear. + +### Branching based on the kind of error is disfavored + +Carbon's design will not prioritize use cases that involve branching based on +error metadata, and Carbon's standard library typically will not make guarantees +about the content of the errors it emits. + +In our experience, it is rare for functions to document a nontrivial contract +regarding the content of the errors they emit, and even rarer for them to +successfully comply with that contract. To a large extent this is a consequence +of convenient error propagation: if you propagate errors from the functions you +call, you no longer have control over the errors you emit. To put the point +another way, propagated errors effectively leak your implementation details. + +Since we expect error propagation to be common, we expect it to be +correspondingly rare for functions to have accurate error contracts, and +correspondingly common for functions to leak their implementation details via +their errors. This in turn means that branching on the content of an error will +normally amount to programmatically inspecting the implementation details of the +function you call, which is inherently brittle, and inhibits code evolution. + +It is certainly possible (with sufficient discipline) to structure a codebase so +that you can reliably branch on certain properties of the error metadata, and +Carbon will support those use cases. However, it will do so as a byproduct of +general-purpose programming facilities such as pattern matching; Carbon will not +provide a separate sugar syntax for pattern-matching error metadata. For +example, if Carbon supports `try`/`catch` statements, they will always have a +single `catch` block, which will be invoked for any error that escapes the `try` +block. + +## TODO: citations + +Joe Duffy's blog post, Herb's paper, ...? From 5d534039edde24e87b2a291a1e59cbfe914f8d63 Mon Sep 17 00:00:00 2001 From: Geoff Romer Date: Thu, 18 Jun 2020 14:09:09 -0700 Subject: [PATCH 02/12] Apply suggestions from code review Co-authored-by: josh11b --- docs/project/principles/error_handling.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/project/principles/error_handling.md b/docs/project/principles/error_handling.md index 8cecc7eccc93e..57dea1454993c 100644 --- a/docs/project/principles/error_handling.md +++ b/docs/project/principles/error_handling.md @@ -21,7 +21,7 @@ to a point that doesn't depend on the discarded state. For example, a function that reads data from a file and validates a checksum might avoid modifying any nonlocal state until validation is successful, and return early if validation fails. This recovery strategy relies on the fact that the likely causes of the -failure are known and bounded (probably a malformed input file or a hardware I/O +failure are known and bounded (probably a malformed input file or an I/O error), which allows us to put a bound on the state that might have been invalidated. @@ -42,7 +42,7 @@ primary goals: handling is never free, and a programming error can occur anywhere. - Because potential programming errors are pervasive, they would have to propagate invisibly, which makes code harder to understand (see - [below](http://TODO)). + [below](#recoverable-errors-are-explicit-at-the-callsite)). - It would inhibit evolution of Carbon libraries, and the Carbon language, by preventing them from changing how they respond to incorrect code. - Similarly, it would prevent Carbon users from choosing different From ce47f2df7efbdaceb4c3fdd4dea0c4b2ba34e1a4 Mon Sep 17 00:00:00 2001 From: Geoffrey Romer Date: Thu, 18 Jun 2020 15:19:04 -0700 Subject: [PATCH 03/12] Respond to review comments, and add missing links. --- docs/project/principles/error_handling.md | 43 +++++++++++++++-------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/docs/project/principles/error_handling.md b/docs/project/principles/error_handling.md index 57dea1454993c..81669bd5d0626 100644 --- a/docs/project/principles/error_handling.md +++ b/docs/project/principles/error_handling.md @@ -21,9 +21,8 @@ to a point that doesn't depend on the discarded state. For example, a function that reads data from a file and validates a checksum might avoid modifying any nonlocal state until validation is successful, and return early if validation fails. This recovery strategy relies on the fact that the likely causes of the -failure are known and bounded (probably a malformed input file or an I/O -error), which allows us to put a bound on the state that might have been -invalidated. +failure are known and bounded (probably a malformed input file or an I/O error), +which allows us to put a bound on the state that might have been invalidated. However, when a programming error is detected, the original cause is neither known nor bounded. For example, if a function dereferences a dangling pointer, @@ -38,14 +37,18 @@ Thus, we expect that supporting recovery from programming errors would provide little or no benefit. Furthermore, it would be harmful to several of Carbon's primary goals: -- It would impose a pervasive performance overhead, because recoverable error +- [Performance-critical software](https://github.com/jonmeow/carbon-lang/blob/proposal-goals/docs/project/goals.md#performance-critical-software): + It would impose a pervasive performance overhead, because recoverable error handling is never free, and a programming error can occur anywhere. -- Because potential programming errors are pervasive, they would have to +- [Code that is easy to read, understand, and write](https://github.com/jonmeow/carbon-lang/blob/proposal-goals/docs/project/goals.md#code-that-is-easy-to-read-understand-and-write): + Because potential programming errors are pervasive, they would have to propagate invisibly, which makes code harder to understand (see [below](#recoverable-errors-are-explicit-at-the-callsite)). -- It would inhibit evolution of Carbon libraries, and the Carbon language, by +- [Software and language evolution](https://github.com/jonmeow/carbon-lang/blob/proposal-goals/docs/project/goals.md#both-software-and-language-evolution): + It would inhibit evolution of Carbon libraries, and the Carbon language, by preventing them from changing how they respond to incorrect code. -- Similarly, it would prevent Carbon users from choosing different +- [Practical safety guarantees and testing mechanisms](https://github.com/jonmeow/carbon-lang/blob/proposal-goals/docs/project/goals.md#practical-safety-guarantees-and-testing-mechanisms): + Similarly, it would prevent Carbon users from choosing different performance/safety tradeoffs for handling programming errors: if an out-of-bounds array access is required to throw an exception, users can't disable bounds checks, regardless of their risk tolerance, because code might @@ -54,7 +57,9 @@ primary goals: #### Examples If Carbon supports assertions and/or contract checking, failed assertions will -not throw exceptions, even as an optional build mode. +not throw exceptions, even as an optional build mode. Assertion failures will +only be presented in ways that don't alter the program state, such as logging, +terminating the program, or trapping into a debugger. ### Memory exhaustion is not recoverable @@ -63,7 +68,8 @@ mechanisms to report memory exhaustion, or support user-defined code that does. Memory exhaustion is not a programming error, and it is feasible to write code that can successfully recover from it. However, the available evidence indicates -that very little C++ code actually does so correctly (TODO: Cite Herb's paper), +that very little C++ code actually does so correctly (see e.g. section 4.3 of +[this paper](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0709r4.pdf)), which suggests that very little C++ code actually needs to do so, and we see no reason to expect Carbon's users to differ in this respect. @@ -78,10 +84,11 @@ API. The `pop` operation on a Carbon queue will return the value removed from the queue. This is in contrast to C++'s `std::queue::pop()`, which does not return -the value popped from the queue, because it is impossible to do so safely if -copying the popped value can throw an out-of-memory error (TODO: cite GotW about -this). Instead, the user must first examine the front of the queue, and then pop -it as a separate operation. Not only is this awkward for users, it means that +the value popped from the queue, because +[that would not be exception-safe](https://isocpp.org/blog/2016/06/quick-q-why-doesnt-stdqueuepop-return-value) +due to the possibility of an out-of-memory error while copying that value. +Instead, the user must first examine the front of the queue, and then pop it as +a separate operation. Not only is this awkward for users, it means that concurrent queues cannot match the API of non-concurrent queues (because separate `front()` and `pop()` calls would create a race condition). @@ -193,6 +200,12 @@ example, if Carbon supports `try`/`catch` statements, they will always have a single `catch` block, which will be invoked for any error that escapes the `try` block. -## TODO: citations +## Other resources -Joe Duffy's blog post, Herb's paper, ...? +Several other groups of language designers have arrived at similar principles. +See e.g. Swift's +[error handling rationale](https://github.com/apple/swift/blob/master/docs/ErrorHandlingRationale.rst), +[Joe Duffy's account](http://joeduffyblog.com/2016/02/07/the-error-model) of +Midori's error model, and Herb Sutter's +[pending proposal](http://wg21.link/P0709) for a new approach to exceptions in +C++. From 9e21afdf62c5c505c443be161b030a692501167c Mon Sep 17 00:00:00 2001 From: Geoffrey Romer Date: Mon, 22 Jun 2020 18:43:45 -0700 Subject: [PATCH 04/12] Overhaul discussion of branching on error metadata, and focus instead on universal error categories. --- docs/project/principles/error_handling.md | 70 ++++++++++++++--------- 1 file changed, 42 insertions(+), 28 deletions(-) diff --git a/docs/project/principles/error_handling.md b/docs/project/principles/error_handling.md index 81669bd5d0626..8d8800ab523c6 100644 --- a/docs/project/principles/error_handling.md +++ b/docs/project/principles/error_handling.md @@ -171,34 +171,48 @@ Given the ubiquity of this use case, Carbon must provide support for it that can be used without altering the structure of the code, or making the non-error-case logic less clear. -### Branching based on the kind of error is disfavored - -Carbon's design will not prioritize use cases that involve branching based on -error metadata, and Carbon's standard library typically will not make guarantees -about the content of the errors it emits. - -In our experience, it is rare for functions to document a nontrivial contract -regarding the content of the errors they emit, and even rarer for them to -successfully comply with that contract. To a large extent this is a consequence -of convenient error propagation: if you propagate errors from the functions you -call, you no longer have control over the errors you emit. To put the point -another way, propagated errors effectively leak your implementation details. - -Since we expect error propagation to be common, we expect it to be -correspondingly rare for functions to have accurate error contracts, and -correspondingly common for functions to leak their implementation details via -their errors. This in turn means that branching on the content of an error will -normally amount to programmatically inspecting the implementation details of the -function you call, which is inherently brittle, and inhibits code evolution. - -It is certainly possible (with sufficient discipline) to structure a codebase so -that you can reliably branch on certain properties of the error metadata, and -Carbon will support those use cases. However, it will do so as a byproduct of -general-purpose programming facilities such as pattern matching; Carbon will not -provide a separate sugar syntax for pattern-matching error metadata. For -example, if Carbon supports `try`/`catch` statements, they will always have a -single `catch` block, which will be invoked for any error that escapes the `try` -block. +### No universal error categories + +Carbon will not establish an error hierarchy or other reusable error vocabulary, +and will not prioritize use cases that involve branching based on the properties +of a propagated error. + +Some languages attempt to impose a hierarchy or some other global classification +scheme for errors, in order to allow code to respond differently to different +kinds of errors, even after the errors have propagated some distance from the +function that originally raised them. However, this practice tends to be quite +brittle, because it almost inevitably requires relying on implementation +details: if a function's contract gives different meanings to different errors +it emits, it generally can't satisfy that contract by blindly propagating errors +from the functions it calls. Conversely, if it doesn't have such a contract, its +callers normally can't differentiate among the errors it emits without depending +on its implementation details. + +It may make sense to distinguish certain categories of errors, if any layer of +the stack can in principle respond to those errors, and the appropriate response +requires only local knowledge. For example, any layer of the stack can respond +to an out-of-memory error by e.g. releasing any unused caches. Similarly, any +layer of the stack can respond to thread cancellation by ceasing any new +computational work and propagating the signal _even if_ it could otherwise +continue despite a failiure at that point. + +However, such cases are caught between the horns of a dilemma: any error that's +universal enough to be meaningful across arbitrary levels of the call stack is +likely to be too pervasive for explicitly-marked propagation to be tolerable. +Both of the above examples have that problem; we've already ruled out +propagating out-of-memory errors because of their pervasiveness, and +cancellation is likely to pose similar challenges (although cancellation can be +ignored, which may simplify the problem somewhat). + +It is certainly possible to structure a codebase so that you can reliably +propagate errors across multiple layers of the stack (so long as you control +those layers), and Carbon will support those use cases. However, it will do so +as a byproduct of general-purpose programming facilities such as pattern +matching; Carbon will not provide a separate sugar syntax for pattern-matching +error metadata, especially if that syntax can encompass multiple +potentially-failing operations. For example, if Carbon supports `try`/`catch` +statements, they will always have a single `catch` block, which will be invoked +for any error that escapes the `try` block. ## Other resources From 827500cf342a9d7b1f68b4f3ab777d3e34a47ec7 Mon Sep 17 00:00:00 2001 From: Geoff Romer Date: Wed, 24 Jun 2020 12:03:42 -0700 Subject: [PATCH 05/12] Apply suggestions from code review Co-authored-by: Dmitri Gribenko --- docs/project/principles/error_handling.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/project/principles/error_handling.md b/docs/project/principles/error_handling.md index 8d8800ab523c6..08db6d9768d55 100644 --- a/docs/project/principles/error_handling.md +++ b/docs/project/principles/error_handling.md @@ -152,7 +152,7 @@ build time. #### Caveats We cannot prevent errors from being ignored if they are conveyed via a -nonstandard mechanism (e.g. `errno`). +custom mechanism (e.g. `errno`). ### Error propagation must be straightforward @@ -194,7 +194,7 @@ requires only local knowledge. For example, any layer of the stack can respond to an out-of-memory error by e.g. releasing any unused caches. Similarly, any layer of the stack can respond to thread cancellation by ceasing any new computational work and propagating the signal _even if_ it could otherwise -continue despite a failiure at that point. +continue despite a failure at that point. However, such cases are caught between the horns of a dilemma: any error that's universal enough to be meaningful across arbitrary levels of the call stack is From ccb94691fbe3f91e16e4d7e544b5e534298ab67f Mon Sep 17 00:00:00 2001 From: Geoffrey Romer Date: Thu, 16 Jul 2020 13:12:09 -0700 Subject: [PATCH 06/12] Add p0084.md, update links, and add a TOC to error_handling.md. --- docs/project/principles/error_handling.md | 30 ++++++++++++++++++----- proposals/p0084.md | 29 ++++++++++++++++++++++ 2 files changed, 53 insertions(+), 6 deletions(-) create mode 100644 proposals/p0084.md diff --git a/docs/project/principles/error_handling.md b/docs/project/principles/error_handling.md index 08db6d9768d55..2089c3588d3d7 100644 --- a/docs/project/principles/error_handling.md +++ b/docs/project/principles/error_handling.md @@ -6,6 +6,24 @@ Exceptions. See /LICENSE for license information. SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception --> + + +- [Principles](#principles) + - [Programming errors are not recoverable](#programming-errors-are-not-recoverable) + - [Examples](#examples) + - [Memory exhaustion is not recoverable](#memory-exhaustion-is-not-recoverable) + - [Examples](#examples-1) + - [Caveats](#caveats) + - [Recoverable errors are explicit in function declarations](#recoverable-errors-are-explicit-in-function-declarations) + - [Caveats](#caveats-1) + - [Recoverable errors are explicit at the callsite](#recoverable-errors-are-explicit-at-the-callsite) + - [Caveats](#caveats-2) + - [Error propagation must be straightforward](#error-propagation-must-be-straightforward) + - [No universal error categories](#no-universal-error-categories) +- [Other resources](#other-resources) + + + ## Principles ### Programming errors are not recoverable @@ -37,17 +55,17 @@ Thus, we expect that supporting recovery from programming errors would provide little or no benefit. Furthermore, it would be harmful to several of Carbon's primary goals: -- [Performance-critical software](https://github.com/jonmeow/carbon-lang/blob/proposal-goals/docs/project/goals.md#performance-critical-software): +- [Performance-critical software](docs/project/goals.md#performance-critical-software): It would impose a pervasive performance overhead, because recoverable error handling is never free, and a programming error can occur anywhere. -- [Code that is easy to read, understand, and write](https://github.com/jonmeow/carbon-lang/blob/proposal-goals/docs/project/goals.md#code-that-is-easy-to-read-understand-and-write): +- [Code that is easy to read, understand, and write](docs/project/goals.md#code-that-is-easy-to-read-understand-and-write): Because potential programming errors are pervasive, they would have to propagate invisibly, which makes code harder to understand (see [below](#recoverable-errors-are-explicit-at-the-callsite)). -- [Software and language evolution](https://github.com/jonmeow/carbon-lang/blob/proposal-goals/docs/project/goals.md#both-software-and-language-evolution): +- [Software and language evolution](docs/project/goals.md#both-software-and-language-evolution): It would inhibit evolution of Carbon libraries, and the Carbon language, by preventing them from changing how they respond to incorrect code. -- [Practical safety guarantees and testing mechanisms](https://github.com/jonmeow/carbon-lang/blob/proposal-goals/docs/project/goals.md#practical-safety-guarantees-and-testing-mechanisms): +- [Practical safety guarantees and testing mechanisms](docs/project/goals.md#practical-safety-guarantees-and-testing-mechanisms): Similarly, it would prevent Carbon users from choosing different performance/safety tradeoffs for handling programming errors: if an out-of-bounds array access is required to throw an exception, users can't @@ -151,8 +169,8 @@ build time. #### Caveats -We cannot prevent errors from being ignored if they are conveyed via a -custom mechanism (e.g. `errno`). +We cannot prevent errors from being ignored if they are conveyed via a custom +mechanism (e.g. `errno`). ### Error propagation must be straightforward diff --git a/proposals/p0084.md b/proposals/p0084.md new file mode 100644 index 0000000000000..0fc0b80ea4624 --- /dev/null +++ b/proposals/p0084.md @@ -0,0 +1,29 @@ +# Principles: Error handling + + + +[Pull request](https://github.com/carbon-language/carbon-lang/pull/84) + +## Table of contents + + + +- [Problem](#problem) +- [Proposal](#proposal) + + + +## Problem + +Error-handling is a pervasive aspect of language and library design, and Carbon +will need a consistent approach to it. + +## Proposal + +Introduce a set of +[principles for error handling](docs/project/principles/error_handling.md). See +that document for details. From 4cc1618f33ef676513e8abb7f5a594ee19de1605 Mon Sep 17 00:00:00 2001 From: Geoffrey Romer Date: Tue, 21 Jul 2020 15:11:56 -0700 Subject: [PATCH 07/12] Respond to reviewer comments. --- docs/project/principles/error_handling.md | 138 +++++++++++----------- 1 file changed, 66 insertions(+), 72 deletions(-) diff --git a/docs/project/principles/error_handling.md b/docs/project/principles/error_handling.md index 2089c3588d3d7..5be889c82e709 100644 --- a/docs/project/principles/error_handling.md +++ b/docs/project/principles/error_handling.md @@ -11,13 +11,11 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Principles](#principles) - [Programming errors are not recoverable](#programming-errors-are-not-recoverable) - [Examples](#examples) - - [Memory exhaustion is not recoverable](#memory-exhaustion-is-not-recoverable) + - [Memory exhaustion is not a recoverable error](#memory-exhaustion-is-not-a-recoverable-error) - [Examples](#examples-1) - [Caveats](#caveats) - [Recoverable errors are explicit in function declarations](#recoverable-errors-are-explicit-in-function-declarations) - - [Caveats](#caveats-1) - [Recoverable errors are explicit at the callsite](#recoverable-errors-are-explicit-at-the-callsite) - - [Caveats](#caveats-2) - [Error propagation must be straightforward](#error-propagation-must-be-straightforward) - [No universal error categories](#no-universal-error-categories) - [Other resources](#other-resources) @@ -29,9 +27,8 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception ### Programming errors are not recoverable The Carbon language and standard library will not use recoverable -error-reporting mechanisms to report programming errors, i.e. errors that are -caused by incorrect user code. Furthermore, Carbon's design will not prioritize -use cases involving recovery from programming errors. +error-reporting mechanisms to report programming errors. Furthermore, Carbon's +design will not prioritize use cases involving recovery from programming errors. Recovering from an error generally consists of discarding any state that might be invalidated by the original cause of the error, and then transferring control @@ -42,12 +39,15 @@ fails. This recovery strategy relies on the fact that the likely causes of the failure are known and bounded (probably a malformed input file or an I/O error), which allows us to put a bound on the state that might have been invalidated. -However, when a programming error is detected, the original cause is neither -known nor bounded. For example, if a function dereferences a dangling pointer, -that might mean that the author of the function forgot to check some condition -before dereferencing, or that the caller incorrectly passed a dangling pointer, -or that some other code released the memory too early, among many other -possibilities. Consequently, the only way to (mostly) reliably recover from a +A _programming error_ is an error caused by incorrect user code, such as failing +to satisfy the preconditions of an operation. When such an error is detected, +the original cause is neither known nor bounded. For example, dereferencing a +dangling pointer is unambiguously a programming error, but it can have many +possible causes. The author of the code might have forgotten to check some +condition before dereferencing, or the caller might have passed a dangling +pointer into the function, or some other code might have released the memory too +early, or any number of other possibilities. Without more information, it's +impossible to know, so the only way to somewhat reliably recover from a programming error is to discard the entire address space and terminate the program. @@ -55,17 +55,17 @@ Thus, we expect that supporting recovery from programming errors would provide little or no benefit. Furthermore, it would be harmful to several of Carbon's primary goals: -- [Performance-critical software](docs/project/goals.md#performance-critical-software): +- [Performance-critical software](/docs/project/goals.md#performance-critical-software): It would impose a pervasive performance overhead, because recoverable error handling is never free, and a programming error can occur anywhere. -- [Code that is easy to read, understand, and write](docs/project/goals.md#code-that-is-easy-to-read-understand-and-write): +- [Code that is easy to read, understand, and write](/docs/project/goals.md#code-that-is-easy-to-read-understand-and-write): Because potential programming errors are pervasive, they would have to propagate invisibly, which makes code harder to understand (see [below](#recoverable-errors-are-explicit-at-the-callsite)). -- [Software and language evolution](docs/project/goals.md#both-software-and-language-evolution): +- [Software and language evolution](/docs/project/goals.md#both-software-and-language-evolution): It would inhibit evolution of Carbon libraries, and the Carbon language, by preventing them from changing how they respond to incorrect code. -- [Practical safety guarantees and testing mechanisms](docs/project/goals.md#practical-safety-guarantees-and-testing-mechanisms): +- [Practical safety guarantees and testing mechanisms](/docs/project/goals.md#practical-safety-guarantees-and-testing-mechanisms): Similarly, it would prevent Carbon users from choosing different performance/safety tradeoffs for handling programming errors: if an out-of-bounds array access is required to throw an exception, users can't @@ -74,19 +74,21 @@ primary goals: #### Examples -If Carbon supports assertions and/or contract checking, failed assertions will -not throw exceptions, even as an optional build mode. Assertion failures will -only be presented in ways that don't alter the program state, such as logging, -terminating the program, or trapping into a debugger. +If Carbon supports contract checking or other forms of assertions, it will not +permit callers to detect and handle assertion failures, even as an optional +build mode. Assertion failures will only be presented in ways that don't alter +the program state, such as logging, terminating the program, or trapping into a +debugger. -### Memory exhaustion is not recoverable +### Memory exhaustion is not a recoverable error -The Carbon standard library will not ordinarily use recoverable error-reporting -mechanisms to report memory exhaustion, or support user-defined code that does. +The Carbon standard library's common-case APIs will not go out of their way to +support treating memory exhaustion as a recoverable error. Memory exhaustion is not a programming error, and it is feasible to write code that can successfully recover from it. However, the available evidence indicates -that very little C++ code actually does so correctly (see e.g. section 4.3 of +that very little C++ code actually does so correctly (for example, see section +4.3 of [this paper](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0709r4.pdf)), which suggests that very little C++ code actually needs to do so, and we see no reason to expect Carbon's users to differ in this respect. @@ -107,8 +109,8 @@ the value popped from the queue, because due to the possibility of an out-of-memory error while copying that value. Instead, the user must first examine the front of the queue, and then pop it as a separate operation. Not only is this awkward for users, it means that -concurrent queues cannot match the API of non-concurrent queues (because -separate `front()` and `pop()` calls would create a race condition). +concurrent queues cannot match the API of non-concurrent queues, because +separate `front()` and `pop()` calls would create a race condition. #### Caveats @@ -118,13 +120,14 @@ However, users may need to build their own libraries on top of it, rather that relying on the Carbon standard library, if they want to take advantage of it. There probably will not be a way to recover from _stack_ exhaustion, because there is no known way of doing that without major drawbacks, and users who can't -tolerate crashing due to stack overflow can normally prevent it via static +tolerate crashing due to stack overflow can normally prevent it using static analysis. ### Recoverable errors are explicit in function declarations Carbon functions that can emit recoverable errors will always be explicitly -marked in all function declarations. +marked in all function declarations, either as part of the return type or as a +separate property of the function. The possibility of emitting recoverable errors is nearly as fundamental to a function's API as its return type, and so Carbon APIs will be substantially @@ -138,28 +141,24 @@ The default should be that functions do not emit errors, because that's the simpler and more efficient behavior, and we also expect it to be the common case. -#### Caveats - -This principle applies only to the mechanisms that Carbon natively provides for -error recovery. We cannot guarantee that errors are explicitly marked if they -are conveyed via some other mechanism (e.g. `errno`-style thread-local storage). - ### Recoverable errors are explicit at the callsite Operations that can emit recoverable errors will always be explicitly marked at the point of use. -If errors can propagate silently (as with exceptions in most languages), it -creates control flow paths that are not visible to the reader of the code, and -it is extremely difficult to reason about procedural code when you aren't aware -of all control flow paths. This would make Carbon code harder to understand, -maintain, and debug. +If errors can propagate silently, as with exceptions in most languages, +functions that they propagate through will have control flow paths that are not +visible to the reader. It is extremely difficult to reason about procedural code +when you aren't aware of all control flow paths, so this approach makes code +harder to understand, maintain, and debug, especially in large cases where +readers may not be familiar with the code above and below them in the call +stack. -Conversely, if errors can be silently ignored (as with error return codes in -many languages), it creates a major risk of accidentally resuming normal -execution without actually recovering from the error (i.e. discarding -invalidated state). This, too, would make it extremely difficult to reason -correctly about Carbon code. +Conversely, if errors can be silently ignored, as with error return codes in +many languages, it creates a major risk of accidentally resuming normal +execution without actually recovering from the error (that is, without +discarding invalidated state). This, too, would make it extremely difficult to +reason correctly about Carbon code. Either possibility would also allow code to evolve in unsafe ways. Changing a function to allow it to emit errors is semantically a breaking change: client @@ -167,11 +166,6 @@ code must now contend with a previously-impossible failure case. Requiring errors to be marked at the callsite ensures that this breakage manifests at build time. -#### Caveats - -We cannot prevent errors from being ignored if they are conveyed via a custom -mechanism (e.g. `errno`). - ### Error propagation must be straightforward Carbon will provide a means to propagate recoverable errors from any function @@ -180,20 +174,20 @@ call to the caller of the enclosing function, with minimal textual overhead. In our experience, it is very common for C++ code to propagate errors across multiple layers of the call stack. C++ exceptions support this natively, and programmers in environments without exceptions usually develop a lightweight way -to propagate errors explicitly, typically via a macro containing a conditional -`return`. In some cases they even resort to using nonstandard language -extensions in order to be able to use this operation within expressions, rather -than only at the statement level. +to propagate errors explicitly, typically by using a macro containing a +conditional `return`. In some cases they even resort to using nonstandard +language extensions in order to be able to use this operation within +expressions, rather than only at the statement level. Given the ubiquity of this use case, Carbon must provide support for it that can -be used without altering the structure of the code, or making the non-error-case -logic less clear. +be used with minimal changes the structure of the code, and without making the +non-error-case logic less clear. ### No universal error categories Carbon will not establish an error hierarchy or other reusable error vocabulary, -and will not prioritize use cases that involve branching based on the properties -of a propagated error. +and will not prioritize use cases that involve classifying and reacting to the +properties of a propagated error. Some languages attempt to impose a hierarchy or some other global classification scheme for errors, in order to allow code to respond differently to different @@ -209,33 +203,33 @@ on its implementation details. It may make sense to distinguish certain categories of errors, if any layer of the stack can in principle respond to those errors, and the appropriate response requires only local knowledge. For example, any layer of the stack can respond -to an out-of-memory error by e.g. releasing any unused caches. Similarly, any -layer of the stack can respond to thread cancellation by ceasing any new -computational work and propagating the signal _even if_ it could otherwise -continue despite a failure at that point. +to an out-of-memory error by releasing any unused caches. Similarly, any layer +of the stack can respond to thread cancellation by ceasing any new computational +work and propagating the signal _even if_ it could otherwise continue despite a +failure at that point. However, such cases are caught between the horns of a dilemma: any error that's universal enough to be meaningful across arbitrary levels of the call stack is likely to be too pervasive for explicitly-marked propagation to be tolerable. Both of the above examples have that problem; we've already ruled out propagating out-of-memory errors because of their pervasiveness, and -cancellation is likely to pose similar challenges (although cancellation can be -ignored, which may simplify the problem somewhat). +cancellation is likely to pose similar challenges, although cancellation can be +ignored, which may simplify the problem somewhat. It is certainly possible to structure a codebase so that you can reliably -propagate errors across multiple layers of the stack (so long as you control -those layers), and Carbon will support those use cases. However, it will do so -as a byproduct of general-purpose programming facilities such as pattern -matching; Carbon will not provide a separate sugar syntax for pattern-matching -error metadata, especially if that syntax can encompass multiple -potentially-failing operations. For example, if Carbon supports `try`/`catch` -statements, they will always have a single `catch` block, which will be invoked -for any error that escapes the `try` block. +propagate errors across multiple layers of the stack so long as you control +those layers, and Carbon will support those use cases. However, it will do so as +a byproduct of general-purpose programming facilities such as pattern matching; +Carbon will not provide a separate sugar syntax for pattern-matching error +metadata, especially if that syntax can encompass multiple potentially-failing +operations. For example, if Carbon supports `try`/`catch` statements, they will +always have a single `catch` block, which will be invoked for any error that +escapes the `try` block. ## Other resources Several other groups of language designers have arrived at similar principles. -See e.g. Swift's +For example, see Swift's [error handling rationale](https://github.com/apple/swift/blob/master/docs/ErrorHandlingRationale.rst), [Joe Duffy's account](http://joeduffyblog.com/2016/02/07/the-error-model) of Midori's error model, and Herb Sutter's From fe0e1e4e72d75d2e0f8786789c687e87baaa6b0f Mon Sep 17 00:00:00 2001 From: Geoffrey Romer Date: Wed, 29 Jul 2020 12:54:22 -0700 Subject: [PATCH 08/12] Respond to reviewer comments. --- docs/project/principles/error_handling.md | 35 ++++++++++++----------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/docs/project/principles/error_handling.md b/docs/project/principles/error_handling.md index 5be889c82e709..9bf62ea323afc 100644 --- a/docs/project/principles/error_handling.md +++ b/docs/project/principles/error_handling.md @@ -30,26 +30,27 @@ The Carbon language and standard library will not use recoverable error-reporting mechanisms to report programming errors. Furthermore, Carbon's design will not prioritize use cases involving recovery from programming errors. -Recovering from an error generally consists of discarding any state that might -be invalidated by the original cause of the error, and then transferring control -to a point that doesn't depend on the discarded state. For example, a function -that reads data from a file and validates a checksum might avoid modifying any -nonlocal state until validation is successful, and return early if validation -fails. This recovery strategy relies on the fact that the likely causes of the -failure are known and bounded (probably a malformed input file or an I/O error), -which allows us to put a bound on the state that might have been invalidated. +Recovering from an error generally consists of discarding or reverting any state +that might be invalidated by the original cause of the error, and then +transferring control to a point that doesn't depend on the discarded state. For +example, a function that reads data from a file and validates a checksum might +avoid modifying any nonlocal state until validation is successful, and return +early if validation fails. This recovery strategy relies on the fact that the +likely causes of the failure are known and bounded (probably a malformed input +file or an I/O error), which allows us to put a bound on the state that might +have been invalidated. A _programming error_ is an error caused by incorrect user code, such as failing to satisfy the preconditions of an operation. When such an error is detected, -the original cause is neither known nor bounded. For example, dereferencing a -dangling pointer is unambiguously a programming error, but it can have many -possible causes. The author of the code might have forgotten to check some -condition before dereferencing, or the caller might have passed a dangling -pointer into the function, or some other code might have released the memory too -early, or any number of other possibilities. Without more information, it's -impossible to know, so the only way to somewhat reliably recover from a -programming error is to discard the entire address space and terminate the -program. +it's not possible for the program to know, or even plausibly guess, what the +original cause is. For example, dereferencing a dangling pointer is +unambiguously a programming error, but it can have many possible causes. The +author of the code might have forgotten to check some condition before +dereferencing, or the caller might have passed a dangling pointer into the +function, or some other code might have released the memory too early, or any +number of other possibilities. Without more information, it's impossible to +know, so the only way to somewhat reliably recover from a programming error is +to discard the entire address space and terminate the program. Thus, we expect that supporting recovery from programming errors would provide little or no benefit. Furthermore, it would be harmful to several of Carbon's From 92181348f3566fc6cd81d7a54a13a0523a74b527 Mon Sep 17 00:00:00 2001 From: Geoffrey Romer Date: Mon, 3 Aug 2020 11:20:08 -0700 Subject: [PATCH 09/12] Respond to reviewer comments --- docs/project/principles/error_handling.md | 60 +++++++++++++---------- 1 file changed, 35 insertions(+), 25 deletions(-) diff --git a/docs/project/principles/error_handling.md b/docs/project/principles/error_handling.md index 9bf62ea323afc..1f45972da88fe 100644 --- a/docs/project/principles/error_handling.md +++ b/docs/project/principles/error_handling.md @@ -36,21 +36,30 @@ transferring control to a point that doesn't depend on the discarded state. For example, a function that reads data from a file and validates a checksum might avoid modifying any nonlocal state until validation is successful, and return early if validation fails. This recovery strategy relies on the fact that the -likely causes of the failure are known and bounded (probably a malformed input -file or an I/O error), which allows us to put a bound on the state that might -have been invalidated. +programmer writing the recovery code can _anticipate_ the error and its likely +causes (probably a malformed input file or an I/O error), which allows them to +put a bound on the state that might have been invalidated. A _programming error_ is an error caused by incorrect user code, such as failing -to satisfy the preconditions of an operation. When such an error is detected, -it's not possible for the program to know, or even plausibly guess, what the -original cause is. For example, dereferencing a dangling pointer is -unambiguously a programming error, but it can have many possible causes. The -author of the code might have forgotten to check some condition before -dereferencing, or the caller might have passed a dangling pointer into the -function, or some other code might have released the memory too early, or any -number of other possibilities. Without more information, it's impossible to -know, so the only way to somewhat reliably recover from a programming error is -to discard the entire address space and terminate the program. +to satisfy the preconditions of an operation. While it is possible to anticipate +such errors, it is very rare to be able to anticipate the causes of those errors +with enough specificity to put a bound on the invalidated state. For example, +dereferencing a dangling pointer is unambiguously a programming error, but it +can have many possible causes. The author of the code might have forgotten to +check some condition before dereferencing, which might mean that only a small +amount of local state is invalid. Or the caller might have passed a dangling +pointer into the function, which means that some of the caller's state is +probably invalid. Or some arbitrarily-distant code might have released the +memory too early, in which case any part of the program that has a copy of the +pointer is invalid. These possibilities are far from exhaustive, and they would +need to be broken down much further to identify exactly which state to discard. + +A programmer might be able to correctly anticipate some number of possible bugs, +and given sufficient heroics they might even be able to programmatically +diagnose them based on their effects in order to invalidate the appropriate +amount of state. But this will almost always be much more difficult, and +probably much more brittle, than simply fixing the anticipated bug or verifying +its absence. Thus, we expect that supporting recovery from programming errors would provide little or no benefit. Furthermore, it would be harmful to several of Carbon's @@ -186,20 +195,21 @@ non-error-case logic less clear. ### No universal error categories -Carbon will not establish an error hierarchy or other reusable error vocabulary, -and will not prioritize use cases that involve classifying and reacting to the -properties of a propagated error. +Carbon will not establish an error hierarchy or other reusable error +classification scheme, and will not prioritize use cases that involve +classifying and reacting to the properties of a propagated error. Some languages attempt to impose a hierarchy or some other global classification -scheme for errors, in order to allow code to respond differently to different -kinds of errors, even after the errors have propagated some distance from the -function that originally raised them. However, this practice tends to be quite -brittle, because it almost inevitably requires relying on implementation -details: if a function's contract gives different meanings to different errors -it emits, it generally can't satisfy that contract by blindly propagating errors -from the functions it calls. Conversely, if it doesn't have such a contract, its -callers normally can't differentiate among the errors it emits without depending -on its implementation details. +scheme for propagatable errors, or encourage libraries to define their own. This +is intended to allow code to respond differently to different kinds of errors, +even after the errors have propagated some distance from the function that +originally raised them. However, this practice tends to be quite brittle, +because it almost inevitably requires relying on implementation details: if a +function's contract gives different meanings to different errors it emits, it +generally can't satisfy that contract by blindly propagating errors from the +functions it calls. Conversely, if it doesn't have such a contract, its callers +normally can't differentiate among the errors it emits without depending on its +implementation details. It may make sense to distinguish certain categories of errors, if any layer of the stack can in principle respond to those errors, and the appropriate response From 6baa4f5a0117be73ad91cce077d43c0aefbf5729 Mon Sep 17 00:00:00 2001 From: Geoff Romer Date: Mon, 3 Aug 2020 11:25:10 -0700 Subject: [PATCH 10/12] Apply suggestions from code review Co-authored-by: austern --- docs/project/principles/error_handling.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/docs/project/principles/error_handling.md b/docs/project/principles/error_handling.md index 1f45972da88fe..f24617d664571 100644 --- a/docs/project/principles/error_handling.md +++ b/docs/project/principles/error_handling.md @@ -95,10 +95,10 @@ debugger. The Carbon standard library's common-case APIs will not go out of their way to support treating memory exhaustion as a recoverable error. -Memory exhaustion is not a programming error, and it is feasible to write code -that can successfully recover from it. However, the available evidence indicates -that very little C++ code actually does so correctly (for example, see section -4.3 of +Memory exhaustion is not a programming error, and it is sometimes feasible to +write code that can successfully recover from it. However, the available +evidence indicates that very little C++ code actually does so correctly (for +example, see section 4.3 of [this paper](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0709r4.pdf)), which suggests that very little C++ code actually needs to do so, and we see no reason to expect Carbon's users to differ in this respect. @@ -112,9 +112,10 @@ API. #### Examples -The `pop` operation on a Carbon queue will return the value removed from the -queue. This is in contrast to C++'s `std::queue::pop()`, which does not return -the value popped from the queue, because +If the Carbon standard library includes queues, the `pop` operation on a +Carbon queue will return the value removed from the queue. This is in contrast +to C++'s `std::queue::pop()`, which does not return the value popped from the +queue, because [that would not be exception-safe](https://isocpp.org/blog/2016/06/quick-q-why-doesnt-stdqueuepop-return-value) due to the possibility of an out-of-memory error while copying that value. Instead, the user must first examine the front of the queue, and then pop it as @@ -124,7 +125,7 @@ separate `front()` and `pop()` calls would create a race condition. #### Caveats -Carbon will probably provide a low-level way to allocate heap memory that makes +Carbon may provide a low-level way to allocate heap memory that makes allocation failure recoverable, because doing so appears to have few drawbacks. However, users may need to build their own libraries on top of it, rather that relying on the Carbon standard library, if they want to take advantage of it. From fe4a8af3c1c099eb2395a53d9a8eae60caf21c0c Mon Sep 17 00:00:00 2001 From: Geoff Romer Date: Mon, 3 Aug 2020 11:37:18 -0700 Subject: [PATCH 11/12] Adopt reviewer suggestion Co-authored-by: josh11b --- docs/project/principles/error_handling.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/project/principles/error_handling.md b/docs/project/principles/error_handling.md index f24617d664571..d6638e3c005ac 100644 --- a/docs/project/principles/error_handling.md +++ b/docs/project/principles/error_handling.md @@ -173,7 +173,7 @@ reason correctly about Carbon code. Either possibility would also allow code to evolve in unsafe ways. Changing a function to allow it to emit errors is semantically a breaking change: client -code must now contend with a previously-impossible failure case. Requiring +code must now contend with a previously impossible failure case. Requiring errors to be marked at the callsite ensures that this breakage manifests at build time. From 8c58e3d378981df5a1fcb39425392444912814bc Mon Sep 17 00:00:00 2001 From: Geoffrey Romer Date: Mon, 3 Aug 2020 13:50:56 -0700 Subject: [PATCH 12/12] Clean up pre-commit errors --- docs/project/principles/error_handling.md | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/docs/project/principles/error_handling.md b/docs/project/principles/error_handling.md index f24617d664571..30f50165fca65 100644 --- a/docs/project/principles/error_handling.md +++ b/docs/project/principles/error_handling.md @@ -112,10 +112,10 @@ API. #### Examples -If the Carbon standard library includes queues, the `pop` operation on a -Carbon queue will return the value removed from the queue. This is in contrast -to C++'s `std::queue::pop()`, which does not return the value popped from the -queue, because +If the Carbon standard library includes queues, the `pop` operation on a Carbon +queue will return the value removed from the queue. This is in contrast to C++'s +`std::queue::pop()`, which does not return the value popped from the queue, +because [that would not be exception-safe](https://isocpp.org/blog/2016/06/quick-q-why-doesnt-stdqueuepop-return-value) due to the possibility of an out-of-memory error while copying that value. Instead, the user must first examine the front of the queue, and then pop it as @@ -125,14 +125,13 @@ separate `front()` and `pop()` calls would create a race condition. #### Caveats -Carbon may provide a low-level way to allocate heap memory that makes -allocation failure recoverable, because doing so appears to have few drawbacks. -However, users may need to build their own libraries on top of it, rather that -relying on the Carbon standard library, if they want to take advantage of it. -There probably will not be a way to recover from _stack_ exhaustion, because -there is no known way of doing that without major drawbacks, and users who can't -tolerate crashing due to stack overflow can normally prevent it using static -analysis. +Carbon may provide a low-level way to allocate heap memory that makes allocation +failure recoverable, because doing so appears to have few drawbacks. However, +users may need to build their own libraries on top of it, rather that relying on +the Carbon standard library, if they want to take advantage of it. There +probably will not be a way to recover from _stack_ exhaustion, because there is +no known way of doing that without major drawbacks, and users who can't tolerate +crashing due to stack overflow can normally prevent it using static analysis. ### Recoverable errors are explicit in function declarations