Skip to content
This repository was archived by the owner on Oct 9, 2018. It is now read-only.

Commit 3672055

Browse files
committed
Error signaling revisions
1 parent 63c8a2d commit 3672055

File tree

2 files changed

+132
-59
lines changed

2 files changed

+132
-59
lines changed

errors/README.md

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,3 @@
11
# Errors
22

3-
Errors are classified along two dimensions:
4-
5-
* **Avoidability**. Can the error be avoided by providing well-formed
6-
input? Unavoidable errors stem from external conditions like the
7-
existence of a file or availability of memory.
8-
9-
* **Recoverability**. Can the operation cleanly exit after the error has occurred?
10-
Unrecoverable errors are usually not avoidable.
11-
12-
Error [signaling](signaling.md) and [handling](handling.md) depends on how the
13-
error is classified.
3+
> **[FIXME]** Add some general text here.

errors/signaling.md

Lines changed: 131 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,59 @@
11
# Signaling errors [RFC]
22

3-
### For unrecoverable errors, `fail!`.
3+
Errors fall into one of three categories:
44

5-
Unrecoverable errors are rare and catastrophic. Since there is no way
6-
to cleanly exit the operation that discovered the error, there is no
7-
sensible return value. Instead, `fail` unwinds the task's stack. See
8-
"Handling Errors" below for guidance on how to use tasks to isolate
9-
unrecoverable errors.
5+
* Catastrophic errors, e.g. out of memory.
6+
* Unpreventable errors, e.g. file not found.
7+
* Preventable errors, e.g. wrong input encoding, index out of bounds.
8+
9+
The signaling strategy is determined by the error category.
10+
11+
## Catastrophic errors
12+
13+
An error is _catastrophic_ if there is no meaningful way for the current task to
14+
continue after the error occurs.
15+
16+
Catastrophic errors are _extremely_ rare, especially outside of `libstd`.
17+
18+
**Canonical examples**: out of memory, stack overflow.
19+
20+
> **[FIXME]** These are believed to be the _only_ catastrophic errors in
21+
> Rust/libstd at the moment. We should confirm, and say so.
22+
23+
### For catastrophic errors, use `fail!`.
24+
25+
Invoking `fail!` causes the current task to fail, which:
26+
27+
* unwinds the task's stack
28+
* poisons any channels or other synchronization connecting the task to others
1029

1130
For example,
1231
[`rt::heap::allocate`](http://static.rust-lang.org/doc/master/std/rt/heap/fn.allocate.html)
1332
fails the task if allocation fails.
1433

15-
### For recoverable but unavoidable errors, use `Result`.
34+
Task failure does not mean that the entire program must abort.
35+
For example, by breaking a program into a parent task that monitors its
36+
children, the program _can_ meaningfully recover from task failures at the
37+
global level. See [the error handling section](handling.md) for guidance
38+
on working with task failure.
39+
40+
## Unpreventable errors
41+
42+
An error is _unpreventable_ if it is caused by circumstances out of the
43+
program's control.
44+
45+
Unpreventable errors are common for operations that involve I/O or other access
46+
to shared resources.
47+
48+
**Canonical example**: file not found.
49+
50+
### For unpreventable errors, use `Result`.
51+
52+
The
53+
[`Result<T,E>` type](http://static.rust-lang.org/doc/master/std/result/index.html)
54+
represents either a success (yielding `T`) or failure (yielding `E`). By
55+
returning a `Result`, a function allows its clients to discover and react to
56+
external circumstances that are obstructing an operation.
1657

1758
The `E` component in `Result<T,E>` should convey details about the external
1859
circumstances that caused the error. Use an `enum` when multiple kinds of errors
@@ -24,67 +65,109 @@ uses the `Result` type pervasively, with
2465
[`IoError`](http://static.rust-lang.org/doc/master/std/io/struct.IoError.html)
2566
providing details when an error occurs.
2667

27-
### For recoverable and avoidable errors, prefer `Result`.
68+
## Preventable errors
69+
70+
An error is _preventable_ if its absence can be guaranteed by restricting the
71+
arguments given to the operation.
2872

29-
Avoidable errors can only arise when a function places additional expectations
30-
on its input beyond its static type. When the error is also recoverable, there
31-
are two choices.
73+
Preventable errors are common for operations that impose constraints on their
74+
parameters beyond their static type.
3275

33-
#### Preferred: returning a `Result`
76+
**Canonical examples**: wrong input encoding, index out of bounds.
77+
78+
### For preventable errors, prefer `Result`.
79+
80+
Preventable errors present API designers with a choice:
81+
* Permit erroneous input, return `Result`, and use the `Err` variant to inform
82+
the client of the error.
83+
* Treat erroneous input as a _contract violation_ (i.e., assertion failure) and `fail!`.
84+
85+
The right choice depends on the _overall design_ of an API: some APIs make it
86+
very easy to obtain and work with valid inputs, in which case working with `Result` would be a needless distraction.
87+
88+
But when in doubt, prefer `Result`.
89+
90+
#### When to return a `Result`
91+
92+
For preventable errors in APIs where
93+
94+
* ensuring input validity ahead of time is difficult or expensive, or
95+
* validity checking is a useful byproduct of attempting an operation,
96+
97+
return a `Result`.
3498

3599
Following the principle of
36100
[returning useful intermediate results](../features/functions-and-methods/output.md),
37-
the function should return a `Result` value with an error component saying
38-
exactly where things went wrong. This information is typically produced when
39-
validating or consuming input anyway; returning it to the client allows for
40-
better error reporting and possibly recovery.
101+
the `Result`'s error component should give detail about which part of the input
102+
was invalid -- information that is typically produced during input validation or
103+
processing anyway.
41104

42105
For example, a function `from_str` for parsing into a given type should return a
43-
`Result` with error component giving the location or span of parsing
44-
failure.
106+
`Result` with error component giving the location or span of parsing failure.
45107

46-
Clients can treat the function's contract as a kind of assertion by
47-
calling `unwrap` on the `Result`, which will produce a failure _in the
48-
client's code_ when the client breaks the contract.
108+
If clients believe they are providing valid input, they can use `unwrap` on the
109+
`Result` to assert as much; this will produce a failure _in the client's
110+
code_ if things go wrong.
49111

50-
#### Failing.
112+
#### When to `fail!`
51113

52-
While returning a `Result` makes for a very clear interface, it can be
53-
burdensome:
114+
For preventable errors in APIs where
54115

55-
* Clients have to `unwrap` the result to assert that they have
56-
satisfied the function's contract.
57-
* The function has to internally propagate the `Result`, and possibly
58-
perform extra cleanup.
116+
* input validity can be easily checked, or
117+
* parts of the API naturally produce valid inputs for other parts, or
118+
* invalid inputs clearly represent a logic error,
59119

60-
Sometimes avoidable, recoverable errors are very rare and clearly
61-
indicate a logic error rather than bad input. Such errors are also
62-
likely to cause task failure at some point (usually because clients
63-
would just `unwrap`).
120+
using `fail!` is acceptable.
64121

65-
In those rare cases, it is acceptable to invoke `fail!` when the error
66-
is discovered. The expectations on the input then become a strong part
67-
of the function's contract, and violating them is always a preventable
68-
bug. The failure conditions should be clearly stated in a `Failure`
69-
section of the function's doc comment.
122+
The expectations on the input then become a _contract_ for the function, and
123+
violating them is akin to an assertion failure -- a bug. The failure conditions
124+
should be clearly stated in a `Failure` section of the function's doc comment.
70125

71-
For example, slice indexing fails on an out of bounds error, which is
72-
recoverable and avoidable but nearly always represents a bug (and is
73-
hopefully rare).
126+
The benefit of using `fail!` is that the signatures of the API's functions are
127+
simpler, and using the API does not require the client to constantly `unwrap`.
128+
129+
For example, slice indexing fails on an out of bounds error, which is a
130+
preventable error and nearly always represents a bug. Moreover, most uses of
131+
array indexing naturally incorporate a bounds check already, e.g. looping over
132+
array indices.
74133

75134
> **[FIXME]** `std` also contains some more dubious examples, like the
76135
> [`to_c_str`](http://static.rust-lang.org/doc/master/std/c_str/trait.ToCStr.html#tymethod.to_c_str)
77136
> method, which fails when the string contains an interior
78137
> `null`. What do we want to say about those?
79138
80-
### Fallible convenience methods. [OPEN]
139+
#### Do not provide both `Result` and `fail!` variants. [RFC]
140+
141+
An API should not provide both `Result`-producing and `fail`ing versions of an
142+
operation. It should provide just the `Result` version, allowing clients to use
143+
`try!` or `unwrap` instead as needed.
144+
145+
There is one exception to this rule, however. Some APIs are strongly oriented
146+
around failure, in the sense that their functions/methods are explicitly
147+
intended as assertions. If there is no other way to check in advance for the
148+
validity of invoking an operation `foo`, however, the API may provide a
149+
`checked_foo` variant that returns a `Result`.
150+
151+
The main examples in `libstd` providing both variants are:
152+
* Channels, which are the primary point of failure propagation between tasks. As
153+
such, calling `recv()` is an _assertion_ that the other end of the channel is
154+
still alive, which will propagate failures from the other end of the
155+
channel. On the other hand, since there is no separate way to atomically test
156+
whether the other end has hung up, channels provide a `recv_opt` variant that
157+
produces a `Result`.
158+
159+
> **[FIXME]** The `_opt` suffix needs to be replaced by a `checked_` prefix.
160+
161+
162+
* `RefCell`, which provides a dynamic version of the borrowing rules. Calling
163+
the `borrow()` method is intended as an assertion that the cell is in a
164+
borrowable state, and will `fail!` otherwise. On the other hand, there is no
165+
separate way to check the state of the `RefCell`, so the module provides a
166+
`try_borrow` variant that produces a `Result`.
81167

82-
> **[OPEN]** This is a big open issue: when should APIs provide
83-
> convenience methods that amount to `unwrap`ping a `Result`? E.g.,
84-
> the `RefCell` type has `borrow` and `try_borrow`, with `.borrow()`
85-
> equivalent to `try_borrow().unwrap()`.
168+
> **[FIXME]** The `try_` prefix needs to be replaced by a `checked_` prefix.
86169
87-
### Avoid `Option` for error signaling.
170+
### Avoid `Option` for error signaling. [RFC]
88171

89172
The `Option` type should be reserved for cases that do not represent errors, but
90173
rather possible outcomes for well-formed inputs under normal circumstances.
@@ -93,8 +176,8 @@ For example,
93176
[the `Vec::pop` method](http://static.rust-lang.org/doc/master/std/vec/struct.Vec.html)
94177
returns an `Option`, yielding `None` when the vector is empty.
95178

96-
Even when there is no interesting error information to return, prefer `Result<T,
97-
()>` to `Option<T>` as a way of signaling that the `Err` case represents an
179+
Even when there is no interesting error information to return, prefer
180+
`Result<T,()>` to `Option<T>` as a way of signaling that the `Err` case represents an
98181
erroneous outcome, not a normal outcome.
99182

100183
Rather than providing a `try_frob` function yielding an `Option`, provide a

0 commit comments

Comments
 (0)