Skip to content

Commit 5d4b752

Browse files
authored
Merge pull request #2504 from rust-lang/fix-error
Fix the Error trait
2 parents d424128 + 4a5e7df commit 5d4b752

File tree

1 file changed

+285
-0
lines changed

1 file changed

+285
-0
lines changed

text/2504-fix-error.md

Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
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

Comments
 (0)