Skip to content

Commit 94390a2

Browse files
committed
Adjust to narrow to the keyword catch
1 parent 9f7dc89 commit 94390a2

File tree

1 file changed

+91
-142
lines changed

1 file changed

+91
-142
lines changed

active/0243-trait-based-exception-handling.md

Lines changed: 91 additions & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ The new constructs are:
1212

1313
* An `?` operator for explicitly propagating "exceptions".
1414

15-
* A `try`..`catch` construct for conveniently catching and handling
15+
* A `catch { ... }` expression for conveniently catching and handling
1616
"exceptions".
1717

1818
The idea for the `?` operator originates from [RFC PR 204][204] by
@@ -39,10 +39,11 @@ These constructs are strict additions to the existing language, and apart from
3939
the issue of keywords, the legality and behavior of all currently existing Rust
4040
programs is entirely unaffected.
4141

42-
The most important additions are a postfix `?` operator for propagating
43-
"exceptions" and a `try`..`catch` block for catching and handling them. By an
44-
"exception", for now, we essentially just mean the `Err` variant of a `Result`.
45-
42+
The most important additions are a postfix `?` operator for
43+
propagating "exceptions" and a `catch {..}` expression for catching
44+
them. By an "exception", for now, we essentially just mean the `Err`
45+
variant of a `Result`, though the Unresolved Questions includes some
46+
discussion of extending to other types.
4647

4748
## `?` operator
4849

@@ -112,54 +113,31 @@ forwarding from `From`). The precise requirements for a conversion to be "like"
112113
a subtyping coercion are an open question; see the "Unresolved questions"
113114
section.
114115

115-
116-
## `try`..`catch`
117-
118-
Like most other things in Rust, and unlike other languages that I know of,
119-
`try`..`catch` is an *expression*. If no exception is thrown in the `try` block,
120-
the `try`..`catch` evaluates to the value of `try` block; if an exception is
121-
thrown, it is passed to the `catch` block, and the `try`..`catch` evaluates to
122-
the value of the `catch` block. As with `if`..`else` expressions, the types of
123-
the `try` and `catch` blocks must therefore unify. Unlike other languages, only
124-
a single type of exception may be thrown in the `try` block (a `Result` only has
125-
a single `Err` type); all exceptions are always caught; and there may only be
126-
one `catch` block. This dramatically simplifies thinking about the behavior of
127-
exception-handling code.
128-
129-
There are two variations on this theme:
130-
131-
1. `try { EXPR }`
132-
133-
In this case the `try` block evaluates directly to a `Result` containing
134-
either the value of `EXPR`, or the exception which was thrown. For instance,
135-
`try { foo()? }` is essentially equivalent to `foo()`. This can be useful if
136-
you want to coalesce *multiple* potential exceptions -
137-
`try { foo()?.bar()?.baz()? }` - into a single `Result`, which you wish to
138-
then e.g. pass on as-is to another function, rather than analyze yourself.
139-
140-
2. `try { EXPR } catch { PAT => EXPR, PAT => EXPR, ... }`
141-
142-
For example:
143-
144-
try {
145-
foo()?.bar()?
146-
} catch {
147-
Red(rex) => baz(rex),
148-
Blue(bex) => quux(bex)
149-
}
150-
151-
Here the `catch` performs a `match` on the caught exception directly, using
152-
any number of refutable patterns. This form is convenient for handling the
153-
exception in-place.
154-
116+
## `catch` expressions
117+
118+
This RFC also introduces an expression form `catch {..}`, which serves
119+
to "scope" the `?` operator. The `catch` operator executes its
120+
associated block. If no exception is thrown, then the result is
121+
`Ok(v)` where `v` is the value of the block. Otherwise, if an
122+
exception is thrown, then the result is `Err(e)`. Note that unlike
123+
other languages, a `catch` block always catches all errors, and they
124+
must all be coercable to a single type, as a `Result` only has a
125+
single `Err` type. This dramatically simplifies thinking about the
126+
behavior of exception-handling code.
127+
128+
Note that `catch { foo()? }` is essentially equivalent to `foo()`.
129+
`catch` can be useful if you want to coalesce *multiple* potential
130+
exceptions -- `try { foo()?.bar()?.baz()? }` -- into a single
131+
`Result`, which you wish to then e.g. pass on as-is to another
132+
function, rather than analyze yourself. (The last example could also
133+
be expressed using a series of `and_then` calls.)
155134

156135
# Detailed design
157136

158137
The meaning of the constructs will be specified by a source-to-source
159-
translation. We make use of an "early exit from any block" feature which doesn't
160-
currently exist in the language, generalizes the current `break` and `return`
161-
constructs, and is independently useful.
162-
138+
translation. We make use of an "early exit from any block" feature
139+
which doesn't currently exist in the language, generalizes the current
140+
`break` and `return` constructs, and is independently useful.
163141

164142
## Early exit from any block
165143

@@ -250,42 +228,6 @@ are merely one way.
250228
}.bar())
251229
}
252230

253-
* Construct:
254-
255-
try {
256-
foo()?.bar()
257-
} catch {
258-
A(a) => baz(a),
259-
B(b) => quux(b)
260-
}
261-
262-
Shallow:
263-
264-
match (try {
265-
foo()?.bar()
266-
}) {
267-
Ok(a) => a,
268-
Err(e) => match e {
269-
A(a) => baz(a),
270-
B(b) => quux(b)
271-
}
272-
}
273-
274-
Deep:
275-
276-
match ('here: {
277-
Ok(match foo() {
278-
Ok(a) => a,
279-
Err(e) => break 'here Err(e.into())
280-
}.bar())
281-
}) {
282-
Ok(a) => a,
283-
Err(e) => match e {
284-
A(a) => baz(a),
285-
B(b) => quux(b)
286-
}
287-
}
288-
289231
The fully expanded translations get quite gnarly, but that is why it's good that
290232
you don't have to write them!
291233

@@ -325,9 +267,63 @@ independently.
325267
These questions should be satisfactorally resolved before stabilizing the
326268
relevant features, at the latest.
327269

270+
## Optional `match` sugar
271+
272+
Originally, the RFC included the ability to `match` the errors caught
273+
by a `catch` by writing `catch { .. } match { .. }`, which could be translated
274+
as follows:
275+
276+
* Construct:
277+
278+
catch {
279+
foo()?.bar()
280+
} match {
281+
A(a) => baz(a),
282+
B(b) => quux(b)
283+
}
284+
285+
Shallow:
286+
287+
match (catch {
288+
foo()?.bar()
289+
}) {
290+
Ok(a) => a,
291+
Err(e) => match e {
292+
A(a) => baz(a),
293+
B(b) => quux(b)
294+
}
295+
}
296+
297+
Deep:
298+
299+
match ('here: {
300+
Ok(match foo() {
301+
Ok(a) => a,
302+
Err(e) => break 'here Err(e.into())
303+
}.bar())
304+
}) {
305+
Ok(a) => a,
306+
Err(e) => match e {
307+
A(a) => baz(a),
308+
B(b) => quux(b)
309+
}
310+
}
311+
312+
However, it was removed for the following reasons:
313+
314+
- The `catch` (originally: `try`) keyword adds the real expressive "step up" here, the `match` (originally: `catch`) was just sugar for `unwrap_or`.
315+
- It would be easy to add further sugar in the future, once we see how `catch` is used (or not used) in practice.
316+
- There was some concern about potential user confusion about two aspects:
317+
- `catch { }` yields a `Result<T,E>` but `catch { } match { }` yields just `T`;
318+
- `catch { } match { }` handles all kinds of errors, unlike `try/catch` in other languages which let you pick and choose.
319+
320+
It may be worth adding such a sugar in the future, or perhaps a
321+
variant that binds irrefutably and does not immediately lead into a
322+
`match` block.
323+
328324
## Choice of keywords
329325

330-
The RFC to this point uses the keywords `try`..`catch`, but there are a number
326+
The RFC to this point uses the keyword `catch`, but there are a number
331327
of other possibilities, each with different advantages and drawbacks:
332328

333329
* `try { ... } catch { ... }`
@@ -358,7 +354,6 @@ Among the considerations:
358354
* Language-level backwards compatibility when adding new keywords. I'm not sure
359355
how this could or should be handled.
360356

361-
362357
## Semantics for "upcasting"
363358

364359
What should the contract for a `From`/`Into` `impl` be? Are these even the right
@@ -401,12 +396,20 @@ Some further thoughts and possibilities on this matter, only as brainstorming:
401396
(This perhaps ties into the subtyping angle: `Ipv4Addr` is clearly not a
402397
supertype of `u32`.)
403398

404-
405399
## Forwards-compatibility
406400

407401
If we later want to generalize this feature to other types such as `Option`, as
408402
described below, will we be able to do so while maintaining backwards-compatibility?
409403

404+
## Monadic do notation
405+
406+
There have been many comparisons drawn between this syntax and monadic
407+
do notation. Before stabilizing, we should determine whether we plan
408+
to make changes to better align this feature with a possible `do`
409+
notation (for example, by removing the implicit `Ok` at the end of a
410+
`catch` block). Note that such a notation would have to extend the
411+
standard monadic bind to accommodate rich control flow like `break`,
412+
`continue`, and `return`.
410413

411414
# Drawbacks
412415

@@ -466,60 +469,6 @@ described below, will we be able to do so while maintaining backwards-compatibil
466469

467470
This RFC doesn't propose doing so at this time, but as it would be an independently useful feature, it could be added as well.
468471

469-
## An additional `catch` form to bind the caught exception irrefutably
470-
471-
The `catch` described above immediately passes the caught exception into a
472-
`match` block. It may sometimes be desirable to instead bind it directly to a
473-
single variable. That might look like this:
474-
475-
try { EXPR } catch IRR-PAT { EXPR }
476-
477-
Where `catch` is followed by any irrefutable pattern (as with `let`).
478-
479-
For example:
480-
481-
try {
482-
foo()?.bar()?
483-
} catch e {
484-
let x = baz(e);
485-
quux(x, e);
486-
}
487-
488-
While it may appear to be extravagant to provide both forms, there is reason to
489-
do so: either form on its own leads to unavoidable rightwards drift under some
490-
circumstances.
491-
492-
The first form leads to rightwards drift if one wishes to do more complex
493-
multi-statement work with the caught exception:
494-
495-
try {
496-
foo()?.bar()?
497-
} catch {
498-
e => {
499-
let x = baz(e);
500-
quux(x, e);
501-
}
502-
}
503-
504-
This single case arm is quite redundant and unfortunate.
505-
506-
The second form leads to rightwards drift if one wishes to `match` on the caught
507-
exception:
508-
509-
try {
510-
foo()?.bar()?
511-
} catch e {
512-
match e {
513-
Red(rex) => baz(rex),
514-
Blue(bex) => quux(bex)
515-
}
516-
}
517-
518-
This `match e` is quite redundant and unfortunate.
519-
520-
Therefore, neither form can be considered strictly superior to the other, and it
521-
may be preferable to simply provide both.
522-
523472
## `throw` and `throws`
524473

525474
It is possible to carry the exception handling analogy further and also add

0 commit comments

Comments
 (0)