From 99b0bce1be2369b69ba45fa48ec306d824285ff7 Mon Sep 17 00:00:00 2001 From: Richard Dodd Date: Wed, 20 Nov 2019 20:39:26 +0000 Subject: [PATCH 1/5] Add box-error-alias RFC --- text/0000-box-error-alias.md | 165 +++++++++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 text/0000-box-error-alias.md diff --git a/text/0000-box-error-alias.md b/text/0000-box-error-alias.md new file mode 100644 index 00000000000..be2931d47a0 --- /dev/null +++ b/text/0000-box-error-alias.md @@ -0,0 +1,165 @@ +- Feature Name: (fill me in with a unique ident, `box_error_alias`) +- Start Date: (fill me in with today's 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; +``` + +# 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 developer, most errors may not occur on the happy path, and so +their performance is inconsequential. Likewise, a low-level library may want fine-grained control +of error handling and recovery, whereas the cli app may only want to handle a small subset of +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 +requires a Display implementation - so all errors can be logged/written/etc, and secondly it +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 +handling in rust should evolve. Instead, it proposes a simple type alias to serve the following +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 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 the rust programmer either creates an alias for +`Box` in some utility module, where each user will use a +sligtly different name, or just writes 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](https://github.com/rust-lang-nursery/failure). +However, this RFC only satisfies the narrow goal of providing a trait alias for `Box`, +allowing more experimentation to take place regarding the specific design of an opaque and more +full-featured error type in the crate ecosystem. + +An example of prior art is the `BoxFuture` type in the `futures` crate. + +# 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`. +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`. + +`BoxError` is simply a synonym for `Box`. 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 { + 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. + +# 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`. + +# 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. From dcd780ce20216c4fa7d51d19a471f519be5b5d7c Mon Sep 17 00:00:00 2001 From: Richard Dodd Date: Wed, 20 Nov 2019 20:48:55 +0000 Subject: [PATCH 2/5] Small redraft + add links --- text/0000-box-error-alias.md | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/text/0000-box-error-alias.md b/text/0000-box-error-alias.md index be2931d47a0..9485cd220dd 100644 --- a/text/0000-box-error-alias.md +++ b/text/0000-box-error-alias.md @@ -1,5 +1,5 @@ -- Feature Name: (fill me in with a unique ident, `box_error_alias`) -- Start Date: (fill me in with today's date, 2019-11-20) +- 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) @@ -19,9 +19,9 @@ Rust error handling has a long and complicated history, and it is still evolving 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 developer, most errors may not occur on the happy path, and so +trait object, whereas for a cli app, most errors may not occur on the happy path, and so their performance is inconsequential. Likewise, a low-level library may want fine-grained control -of error handling and recovery, whereas the cli app may only want to handle a small subset of +of error handling and recovery, whereas a cli app may only want to handle a small subset of 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 @@ -33,24 +33,22 @@ handling in rust should evolve. Instead, it proposes a simple type alias to serv 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 noise and cognitive load, + 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 the rust programmer either creates an alias for +Currently what happens in practice is rust programmers either create an alias for `Box` in some utility module, where each user will use a -sligtly different name, or just writes out the full type that `BoxFuture` aliases. +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](https://github.com/rust-lang-nursery/failure). +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`, allowing more experimentation to take place regarding the specific design of an opaque and more full-featured error type in the crate ecosystem. -An example of prior art is the `BoxFuture` type in the `futures` crate. - # Guide-level explanation [guide-level-explanation]: #guide-level-explanation @@ -150,6 +148,8 @@ There are many error models in the rust ecosystem, including many type-erased er 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 @@ -163,3 +163,8 @@ There are many ways that the Error trait could be enhanced, for example by provi `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 From 02b459b0ef9463ad75429ad97d1e289f86e565d7 Mon Sep 17 00:00:00 2001 From: Richard Dodd Date: Wed, 20 Nov 2019 21:01:27 +0000 Subject: [PATCH 3/5] Fix some grammar --- text/0000-box-error-alias.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/text/0000-box-error-alias.md b/text/0000-box-error-alias.md index 9485cd220dd..b07f1c4a829 100644 --- a/text/0000-box-error-alias.md +++ b/text/0000-box-error-alias.md @@ -19,17 +19,17 @@ Rust error handling has a long and complicated history, and it is still evolving 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 +trait object, whereas for a CLI app, most errors may not occur on the happy path, and so 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 +of error handling and recovery, whereas a CLI app may only want to handle a small subset of 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 -requires a Display implementation - so all errors can be logged/written/etc, and secondly it +The error trait in Rust's standard library provides a helpful way to perform two tasks: firstly it +requires a `Display` implementation - so all errors can be logged/written/etc, and secondly it 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 -handling in rust should evolve. Instead, it proposes a simple type alias to serve the following +is ongoing in Rust, and this RFC does not attempt to resolve the discussion around how error +handling in Rust should evolve. Instead, it proposes a simple type alias to serve the following motivations 1. A unified name for boxed errors, to make them easier to recognise, @@ -38,7 +38,7 @@ motivations 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 +Currently what happens in practice is Rust programmers either create an alias for `Box` in some utility module, where each user will use a sligtly different name, or just write out the full type that `BoxFuture` aliases. @@ -70,27 +70,27 @@ functionality of errors may be further constrained by the requirements of the pr 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 +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 +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 +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`. -The trait object obscures the actual type, instead presenting the Error interface, and is easily +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`. `BoxError` is simply a synonym for `Box`. Here are some examples showing its usefulness. -## Example: Returning from `main` +## Example: Simple database function. 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. @@ -144,7 +144,7 @@ hypothetical `AnyError` are # Prior art [prior-art]: #prior-art -There are many error models in the rust ecosystem, including many type-erased error types. Crates +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. @@ -153,7 +153,7 @@ An example of prior art of name choice is the [`BoxFuture`] type in the [`future # Unresolved questions [unresolved-questions]: #unresolved-questions - - Should the error be `Send + Sync`. It seems like a good default, and nothing is stopping rust + - 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`. # Future possibilities From 32a5e8a8ed919df1775dcf31aa75a92119da6868 Mon Sep 17 00:00:00 2001 From: Richard Dodd Date: Wed, 20 Nov 2019 21:06:26 +0000 Subject: [PATCH 4/5] Better wording --- text/0000-box-error-alias.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/0000-box-error-alias.md b/text/0000-box-error-alias.md index b07f1c4a829..8e41ef34f24 100644 --- a/text/0000-box-error-alias.md +++ b/text/0000-box-error-alias.md @@ -111,8 +111,8 @@ 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. + 1. Increases std api surface area, although semantically there is no change since it only a type + alias is added. 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). From b50f8c784589a68bb22ee59c887664e005f0d3e0 Mon Sep 17 00:00:00 2001 From: Richard Dodd Date: Fri, 22 Nov 2019 10:17:34 +0000 Subject: [PATCH 5/5] Boxed trait objects default to static in the absence of a lifetime --- text/0000-box-error-alias.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/text/0000-box-error-alias.md b/text/0000-box-error-alias.md index 8e41ef34f24..0462e1f82b2 100644 --- a/text/0000-box-error-alias.md +++ b/text/0000-box-error-alias.md @@ -9,9 +9,11 @@ Add a type alias to std::error of the form ```rust -pub type BoxError = Box; +pub type BoxError = Box; ``` +which given the absence of a lifetime is also `'static`. + # Motivation [motivation]: #motivation @@ -39,7 +41,7 @@ motivations error, aiding discoverability. Currently what happens in practice is Rust programmers either create an alias for -`Box` in some utility module, where each user will use a +`Box` 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) @@ -87,7 +89,7 @@ The trait object obscures the actual type, instead presenting the `Error` interf created from any error using the `impl From<'a, E: Error + Send + Sync + 'a> for Box`. -`BoxError` is simply a synonym for `Box`. Here are some examples +`BoxError` is simply a synonym for `Box`. Here are some examples showing its usefulness. ## Example: Simple database function. @@ -154,7 +156,7 @@ An example of prior art of name choice is the [`BoxFuture`] type in the [`future [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`. + programmers simply not using the alias if they want a `Box`. # Future possibilities [future-possibilities]: #future-possibilities