|
| 1 | +- Feature Name: `fix_error` |
| 2 | +- Start Date: 2018-07-18 |
| 3 | +- RFC PR: [rust-lang/rfcs#2504](https://github.com/rust-lang/rfcs/pull/2504) |
| 4 | +- Rust Issue: [rust-lang/rust#53487](https://github.com/rust-lang/rust/issues/53487) |
| 5 | + |
| 6 | +# Summary |
| 7 | +[summary]: #summary |
| 8 | + |
| 9 | +Change the `std::error::Error` trait to improve its usability. Introduce a |
| 10 | +backtrace module to the standard library to provide a standard interface for |
| 11 | +backtraces. |
| 12 | + |
| 13 | +# Motivation |
| 14 | +[motivation]: #motivation |
| 15 | + |
| 16 | +The `Error` trait has long been known to be flawed in several respects. In |
| 17 | +particular: |
| 18 | + |
| 19 | +1. The required `description` method is limited, usually, to returning static |
| 20 | +strings, and has little utility that isn't adequately served by the required |
| 21 | +`Display` impl for the error type. |
| 22 | +2. The signature of the `cause` method does not allow the user to downcast the |
| 23 | +cause type, limiting the utility of that method unnecessarily. |
| 24 | +3. It provides no standard API for errors that contain backtraces (as some |
| 25 | +users' errors do) to expose their backtraces to end users. |
| 26 | + |
| 27 | +We propose to fix this by deprecating the existing methods of `Error` and adding |
| 28 | +two new, provided methods. As a result, the undeprecated portion of the `Error` |
| 29 | +trait would look like this: |
| 30 | + |
| 31 | +```rust |
| 32 | +trait Error: Display + Debug { |
| 33 | + fn backtrace(&self) -> Option<&Backtrace> { |
| 34 | + None |
| 35 | + } |
| 36 | + |
| 37 | + fn source(&self) -> Option<&dyn Error + 'static> { |
| 38 | + None |
| 39 | + } |
| 40 | +} |
| 41 | +``` |
| 42 | + |
| 43 | +# Guide-level explanation |
| 44 | +[guide-level-explanation]: #guide-level-explanation |
| 45 | + |
| 46 | +## The new API of the Error trait |
| 47 | + |
| 48 | +The new API provides three main components: |
| 49 | + |
| 50 | +1. The Display and Debug impls, for printing error messages. Ideally, the |
| 51 | + Display API would be focused on *end user* messages, whereas the Debug impl |
| 52 | + would contain information relevant to the programmer. |
| 53 | +2. The backtrace method. If the Error contains a backtrace, it should be exposed |
| 54 | + through this method. Errors are not required to contain a backtrace, and are |
| 55 | + not expected to. |
| 56 | +3. The `source` method. This returns another Error type, which is the underlying |
| 57 | + source of this error. If this error has no underlying source (that is, it is |
| 58 | + the "root source" of the error), this method should return none. |
| 59 | + |
| 60 | +## The backtrace API |
| 61 | + |
| 62 | +This RFC adds a new `backtrace` module to std, with one type, with this API: |
| 63 | + |
| 64 | +```rust |
| 65 | +pub struct Backtrace { |
| 66 | + // ... |
| 67 | +} |
| 68 | + |
| 69 | +impl Backtrace { |
| 70 | + // Capture the backtrace for the current stack if it is supported on this |
| 71 | + // platform. |
| 72 | + // |
| 73 | + // This will respect backtrace controlling environment variables. |
| 74 | + pub fn capture() -> Backtrace { |
| 75 | + // ... |
| 76 | + } |
| 77 | + |
| 78 | + // Capture the backtrace for the current stack if it is supported on this |
| 79 | + // platform. |
| 80 | + // |
| 81 | + // This will ignore backtrace controlling environment variables. |
| 82 | + pub fn force_capture() -> Backtrace { |
| 83 | + // ... |
| 84 | + } |
| 85 | + |
| 86 | + pub fn status(&self) -> BacktraceStatus { |
| 87 | + // ... |
| 88 | + } |
| 89 | +} |
| 90 | + |
| 91 | +impl Display for Backtrace { |
| 92 | + // ... |
| 93 | +} |
| 94 | + |
| 95 | +impl Debug for Backtrace { |
| 96 | + // ... |
| 97 | +} |
| 98 | + |
| 99 | +#[non_exhaustive] |
| 100 | +pub enum BacktraceStatus { |
| 101 | + Unsupported, |
| 102 | + Disabled, |
| 103 | + Captured |
| 104 | +} |
| 105 | +``` |
| 106 | + |
| 107 | +This minimal initial API is just intended for printing backtraces for end users. |
| 108 | +In time, this may grow the ability to visit individual frames of the backtrace. |
| 109 | + |
| 110 | +### Backtrace controlling environment variables |
| 111 | + |
| 112 | +Today, the `RUST_BACKTRACE` controls backtraces generated by panics. After this |
| 113 | +RFC, it also controls backtraces generated in the standard library: no backtrace |
| 114 | +will be generated when calling `Backtrace::capture` unless this variable is set. |
| 115 | +On the other hand, `Backtrace::force_capture` will ignore this variable. |
| 116 | + |
| 117 | +Two additional variables will be added: `RUST_PANIC_BACKTRACE` and |
| 118 | +`RUST_LIB_BACKTRACE`: these will independently override the behavior of |
| 119 | +`RUST_BACKTRACE` for backtraces generated for panics and from the std API. |
| 120 | + |
| 121 | +## The transition plan |
| 122 | + |
| 123 | +Deprecating both `cause` and `description` is a backward compatible change, and |
| 124 | +adding provided methods `backtrace` and `source` is also backward compatible. |
| 125 | +We can make these changes unceremoniously, and the `Error` trait will be much |
| 126 | +more functional. |
| 127 | + |
| 128 | +We also change the default definition of `cause`, even though it is deprecated: |
| 129 | + |
| 130 | +```rust |
| 131 | +fn cause(&self) -> Option<&dyn Error> { |
| 132 | + self.source() |
| 133 | +} |
| 134 | +``` |
| 135 | + |
| 136 | +This way, if an error defines `source`, someone calling the deprecated `cause` |
| 137 | +API will still get the correct cause type, even though they can't downcast it. |
| 138 | + |
| 139 | +## Stability |
| 140 | + |
| 141 | +The addition of `source` and the deprecation of `cause` will be instantly |
| 142 | +stabilized after implementing this RFC. |
| 143 | + |
| 144 | +The addition of the backtrace method and the entire backtrace API will be left |
| 145 | +unstable under the `backtrace` feature for now. |
| 146 | + |
| 147 | +# Reference-level explanation |
| 148 | +[reference-level-explanation]: #reference-level-explanation |
| 149 | + |
| 150 | +## Why `cause` -> `source` |
| 151 | + |
| 152 | +The problem with the existing `cause` API is that the error it returns is not |
| 153 | +`'static`. This means it is not possible to downcast the error trait object, |
| 154 | +because downcasting can only be done on `'static` trait objects (for soundness |
| 155 | +reasons). |
| 156 | + |
| 157 | +## Note on backtraces |
| 158 | + |
| 159 | +The behavior of backtraces is somewhat platform specific, and on certain |
| 160 | +platforms backtraces may contain strange and inaccurate information. The |
| 161 | +backtraces provided by the standard library are designed for user display |
| 162 | +purposes only, and not guaranteed to provide a perfect representation of the |
| 163 | +program state, depending on the capabilities of the platform. |
| 164 | + |
| 165 | +## How this impacts failure |
| 166 | + |
| 167 | +The failure crate defines a `Fail` trait with an API similar to (but not |
| 168 | +exactly like) the API proposed here. In a breaking change to failure, we would |
| 169 | +change that trait to be an extension trait to `Error`: |
| 170 | + |
| 171 | +```rust |
| 172 | +// Maybe rename to ErrorExt? |
| 173 | +trait Fail: Error + Send + Sync + 'static { |
| 174 | + // various provided helper methods |
| 175 | +} |
| 176 | + |
| 177 | +impl<E: Error + Send + Sync + 'static> Fail for E { |
| 178 | + |
| 179 | +} |
| 180 | +``` |
| 181 | + |
| 182 | +Instead of providing a derive for Fail, failure would provide a derive for the |
| 183 | +std library Error trait, e.g.: |
| 184 | + |
| 185 | +```rust |
| 186 | +#[derive(Debug, Display, Error)] |
| 187 | +#[display = "My display message."] |
| 188 | +struct MyError { |
| 189 | + #[error(source)] |
| 190 | + underlying: io::Error, |
| 191 | + backtrace: Backtrace, |
| 192 | +} |
| 193 | +``` |
| 194 | + |
| 195 | +The exact nature of the new failure API would be determined by the maintainers |
| 196 | +of failure, it would not be proscribed by this RFC. This section is just to |
| 197 | +demonstrate that failure could still work using the std Error trait. |
| 198 | + |
| 199 | +# Drawbacks |
| 200 | +[drawbacks]: #drawbacks |
| 201 | + |
| 202 | +This causes some churn, as users who are already using one of the deprecated |
| 203 | +methods will be encouraged (by warnings) to change their code, and library |
| 204 | +authors will need to revisit whether they should override one of the new |
| 205 | +methods. |
| 206 | + |
| 207 | +# Rationale and alternatives |
| 208 | +[rationale-and-alternatives]: #rationale-and-alternatives |
| 209 | + |
| 210 | +## Provide a new error trait |
| 211 | + |
| 212 | +The most obvious alternative to this RFC would be to provide an entirely new |
| 213 | +error trait. This could make deeper changes to the API of the trait than we are |
| 214 | +making here. For example, we could take an approach like `failure` has, and |
| 215 | +impose stricter bounds on all implementations of the trait: |
| 216 | + |
| 217 | +```rust |
| 218 | +trait Fail: Display + Debug + Send + Sync + 'static { |
| 219 | + fn cause(&self) -> Option<&dyn Fail> { |
| 220 | + None |
| 221 | + } |
| 222 | + |
| 223 | + fn backtrace(&self) -> Option<&Backtrace> { |
| 224 | + None |
| 225 | + } |
| 226 | +} |
| 227 | +``` |
| 228 | + |
| 229 | +Doing this would allow us to assemble a more perfect error trait, rather than |
| 230 | +limiting us to the changes we can make backwards compatibly to the existing |
| 231 | +trait. |
| 232 | + |
| 233 | +However, it would be much more disruptive to the ecosystem than changing the |
| 234 | +existing trait. We've already seen some friction with failure and other APIs |
| 235 | +(like serde's) that expect to receive something that implements `Error`. Right |
| 236 | +now, we reason that the churn is not worth slight improvements that wouldn't be |
| 237 | +compatible with the Error trait as it exists. |
| 238 | + |
| 239 | +In the future, if these changes are not enough to resolve the warts with the |
| 240 | +Error trait, we could follow this alternative: we would deprecate the Error |
| 241 | +trait and introduce a new trait then. That is, accepting this RFC now does not |
| 242 | +foreclose on this alternative later. |
| 243 | + |
| 244 | +## Bikeshedding the name of `source` |
| 245 | + |
| 246 | +The replacement for `cause` could have another name. The only one the RFC author |
| 247 | +came up with is `origin`. |
| 248 | + |
| 249 | +# Prior art |
| 250 | +[prior-art]: #prior-art |
| 251 | + |
| 252 | +This proposal is largely informed by our experience with the existing Error |
| 253 | +trait API, and from the experience of the `failure` experiment. The main |
| 254 | +conclusions we drew: |
| 255 | + |
| 256 | +1. The current Error trait has serious flaws. |
| 257 | +2. The `Fail` trait in failure has a better API. |
| 258 | +3. Having multiple error traits in the ecosystem (e.g. both `Error` and `Fail`) |
| 259 | +can be very disruptive. |
| 260 | + |
| 261 | +This RFC threads the needle between the problem with the Error trait and the |
| 262 | +problems caused by defining a new trait. |
| 263 | + |
| 264 | +# Unresolved questions |
| 265 | +[unresolved-questions]: #unresolved-questions |
| 266 | + |
| 267 | +## Backtrace API |
| 268 | + |
| 269 | +This RFC intentionally proposes a most minimal API. There are a number of API |
| 270 | +extensions we could consider in the future. Prominent examples: |
| 271 | + |
| 272 | +1. Extending the backtrace API to allow programmatic iteration of backtrace |
| 273 | +frames and so on. |
| 274 | +2. Providing derives for traits like `Display` and `Error` in the standard |
| 275 | +libray. |
| 276 | +3. Providing helper methods on `Error` that have been experimented with in |
| 277 | +failure, such as the causes iterator. |
| 278 | + |
| 279 | +None of these are proposed as a part of *this* RFC, and would have a future RFC |
| 280 | +discussion. |
| 281 | + |
| 282 | +Additionally, the choice to implement nullability internal to backtrace may |
| 283 | +prove to be a mistake: during the period when backtrace APIs are only available |
| 284 | +on nightly, we will gain more experience and possible change backtrace's |
| 285 | +constructors to return an `Option<Backtrace>` instead. |
0 commit comments