-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Box error alias #2820
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
richard-uk1
wants to merge
5
commits into
rust-lang:master
Choose a base branch
from
richard-uk1:box-error-alias
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Box error alias #2820
Changes from 2 commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
99b0bce
Add box-error-alias RFC
richard-uk1 dcd780c
Small redraft + add links
richard-uk1 02b459b
Fix some grammar
richard-uk1 32a5e8a
Better wording
richard-uk1 b50f8c7
Boxed trait objects default to static in the absence of a lifetime
richard-uk1 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
- Feature Name: `box_error_alias` | ||
- Start Date: 2019-11-20 | ||
- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) | ||
- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) | ||
|
||
# Summary | ||
[summary]: #summary | ||
|
||
Add a type alias to std::error of the form | ||
|
||
```rust | ||
pub type BoxError = Box<dyn Error + Send + Sync + 'static>; | ||
``` | ||
|
||
# Motivation | ||
[motivation]: #motivation | ||
|
||
Rust error handling has a long and complicated history, and it is still evolving right now. This | ||
reflects the fact that errors perform a multitude of functions, and these different functions | ||
require different implementations: for example, if errors are regularly encountered in a real-time | ||
thread, then one would not want their creation to involve an allocation or the indirection of a | ||
trait object, whereas for a cli app, most errors may not occur on the happy path, and so | ||
richard-uk1 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
their performance is inconsequential. Likewise, a low-level library may want fine-grained control | ||
of error handling and recovery, whereas a cli app may only want to handle a small subset of | ||
richard-uk1 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
errors, simply printing a message to screen for the rest. | ||
|
||
The error trait in rust's standard library provides a helpful way to perform two tasks: firstly it | ||
richard-uk1 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
requires a Display implementation - so all errors can be logged/written/etc, and secondly it | ||
richard-uk1 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
provides the error with the option of exposing another inner error, thereby allowing chains of | ||
errors to be created. [Research into the best way to represent errors](https://internals.rust-lang.org/t/thoughts-on-error-context-in-error-handling-libraries/10349/4) | ||
is ongoing in rust, and this RFC does not attempt to resolve the discussion around how error | ||
richard-uk1 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
handling in rust should evolve. Instead, it proposes a simple type alias to serve the following | ||
richard-uk1 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
motivations | ||
|
||
1. A unified name for boxed errors, to make them easier to recognise, | ||
2. A short descriptive name for error trait objects, to reduce code noise and cognitive load, | ||
3. A place to show the value of making errors `Send + Sync + 'static`, and | ||
3. A place in the standard library to document the pattern of using a trait object to return any | ||
error, aiding discoverability. | ||
|
||
Currently what happens in practice is rust programmers either create an alias for | ||
`Box<dyn Error + Send + Sync + 'static>` in some utility module, where each user will use a | ||
sligtly different name, or just write out the full type that `BoxFuture` aliases. | ||
|
||
This idea evolved on a [thread in irlo](https://internals.rust-lang.org/t/proposal-add-std-boxerror/10953) | ||
before this RFC was written, and there names such as `AnyError` were suggested, as well as using an | ||
opaque object with a raw pointer as is the case in the [`failure` crate][`failure`]. | ||
However, this RFC only satisfies the narrow goal of providing a trait alias for `Box<Error + ..>`, | ||
allowing more experimentation to take place regarding the specific design of an opaque and more | ||
full-featured error type in the crate ecosystem. | ||
|
||
# Guide-level explanation | ||
[guide-level-explanation]: #guide-level-explanation | ||
|
||
Once you start doing anything more advanced than arithmetic with a computer, you will encounter | ||
routines of the computer that may fail. The source of these failures may be hardware-related (e.g. | ||
a resistor has shorted in your temperature sensor peripheral), a bug in some code you (or someone | ||
else) wrote, the misuse of an interface, or a network failure, amongst many others. Ideally we | ||
design programming languages and libraries so that programming mistakes are caught when the program | ||
is compiled rather than at run-time, but obviously this is not always possible. It is therefore | ||
necessary to have run-time error handling. | ||
|
||
Not all errors are the same: sometimes an error can be corrected on the fly, sometimes an operation | ||
must be re-tried (e.g. a tcp packet failed to arrive), and sometimes the error represents something | ||
catastrophic and the best response is to terminate the program with a useful error message. These | ||
different errors require different types of data: an error that will be instantly corrected should | ||
be able to choose whether it is stored on the heap or the stack, whereas for an error that will | ||
terminate the program the performance impact of a trait object/allocation will be negligable. The | ||
functionality of errors may be further constrained by the requirements of the programming | ||
environment: an embedded chip with low memory performing tasks in realtime may not have run-time | ||
memory allocation as an option. | ||
|
||
In general, rust programmers will create different types to handle these different situations. The type | ||
for a recoverable error may just be some fixed-size block of data, whereas a more serious error may | ||
contain references to descriptions of the error or other errors that cause it. Rust provides the | ||
`Error` trait, which includes converting the error into a string and referencing any underlying | ||
error, but rust programmers can still implement this trait even though they don't want to be allocating | ||
strings or inner errors, since it is the caller of the methods who causes the allocations/etc. In | ||
this way, rust allows all errors to be able to describe themselves, which is very useful during | ||
development and prototyping, as well as helping to support different use cases for library code. | ||
|
||
Whist the `Error` trait helps error authors to provide self-describing errors, it does not help | ||
code using these errors to easily combine them with other errors of different types, and displaying | ||
the contents of an error that might be one of multiple types. The standard way to solve this | ||
problem is to create a trait object behind a thick pointer, hence the signature `Box<dyn Error..>`. | ||
The trait object obscures the actual type, instead presenting the Error interface, and is easily | ||
created from any error using the `impl From<'a, E: Error + Send + Sync + 'a> for Box<dyn Error + | ||
Send + Sync + 'a>`. | ||
|
||
`BoxError` is simply a synonym for `Box<dyn Error + Send + Sync + 'static>`. Here are some examples | ||
showing its usefulness. | ||
|
||
## Example: Returning from `main` | ||
|
||
Here we are getting a database version from a file and returning it. We are in the prototyping | ||
stage and don't want to devote too much time to complex error handling. | ||
|
||
```rust | ||
fn db_version() -> Result<usize, BoxError> { | ||
let version = fs::read_to_string("/path/to/version/file")? | ||
.parse()?; | ||
Ok(version) | ||
} | ||
``` | ||
|
||
# Reference-level explanation | ||
[reference-level-explanation]: #reference-level-explanation | ||
|
||
The implementation is very straight forward, simply the code in the [summary]. | ||
|
||
# Drawbacks | ||
[drawbacks]: #drawbacks | ||
|
||
1. Increases std api surface area, although semantically there is no change since it is a type | ||
alias. | ||
2. Could potentially encourage people to use it instead of a specialized type like | ||
[`anyhow::Error`](https://docs.rs/anyhow/latest/anyhow/struct.Error.html). | ||
|
||
- I would challenge this drawback with the argument that even after the addition of a | ||
hypothetical `AnyError`, there will still be times when a `BoxError` is appropriate for its | ||
simplicity. It may turn out that this satisfies 99% of use cases for type-erased errors, and | ||
any more specialized solutions can live in crates. | ||
|
||
# Rationale and alternatives | ||
[rationale-and-alternatives]: #rationale-and-alternatives | ||
|
||
This RFC proposes a very small addition that provides a type alias that many people define | ||
manually. The only real alternative is to do nothing and maybe land a more specialized type-erased | ||
error type in the future. | ||
|
||
The rationale for not including a specialized error type is that it is not clear what the best | ||
design for it would be, especially since the story around backtraces is not yet finalised. It's | ||
also not clear if a specialized type is necessary. The disadvantages of `BoxError` over a | ||
hypothetical `AnyError` are | ||
|
||
1. An increased memory footprint (increasing the size of `Result` in the case that the `Ok` | ||
variant is small), but with fewer memory indirections. If this is an issue, then a concrete | ||
error type is more appropriate. | ||
2. The `Debug` implementation defers to the inner error type. The hypothetical `AnyError` could | ||
use the inner error's `Display` type, leading to better messages in the | ||
`fn main() -> Result<(), AnyError>` case. | ||
|
||
|
||
# Prior art | ||
[prior-art]: #prior-art | ||
|
||
There are many error models in the rust ecosystem, including many type-erased error types. Crates | ||
include [`anyhow`] and [`failure`] among others. Prior art for a type synonym is the many crates | ||
that define it internally. | ||
|
||
An example of prior art of name choice is the [`BoxFuture`] type in the [`futures`] crate. | ||
|
||
# Unresolved questions | ||
[unresolved-questions]: #unresolved-questions | ||
|
||
- Should the error be `Send + Sync`. It seems like a good default, and nothing is stopping rust | ||
programmers simply not using the alias if they want a `Box<dyn Error + 'static>`. | ||
|
||
# Future possibilities | ||
[future-possibilities]: #future-possibilities | ||
|
||
There are many ways that the Error trait could be enhanced, for example by providing a dedicated | ||
`AnyError` type, or providing a method like `Error::wrap` that would take an error, and make it the | ||
source of an `AnyError`. This RFC attempts to be very focused on a small change, and so these ideas | ||
are better discussed elsewhere. | ||
|
||
[`anyhow`]: https://github.com/dtolnay/anyhow | ||
[`failure`]: https://github.com/rust-lang-nursery/failure | ||
[`BoxFuture`]: https://docs.rs/futures/0.3.1/futures/future/type.BoxFuture.html | ||
[`futures`]: https://github.com/rust-lang-nursery/futures-rs |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.