You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: active/0000-trait-based-exception-handling.md
+61-11
Original file line number
Diff line number
Diff line change
@@ -53,8 +53,8 @@ chaining method calls which may each "throw an exception":
53
53
54
54
foo()?.bar()?.baz()
55
55
56
-
(Naturally, in this case the types of the "exceptions thrown by" `foo()` and
57
-
`bar()` must unify.)
56
+
Naturally, in this case the types of the "exceptions thrown by" `foo()` and
57
+
`bar()` must unify. Like the current `try!()` macro, the `?` operator will also perform an implicit "upcast" on the exception type.
58
58
59
59
When used outside of a `try` block, the `?` operator propagates the exception to
60
60
the caller of the current function, just like the current `try!` macro does. (If
@@ -79,6 +79,23 @@ tracked in the type system, and there is no silent propagation of exceptions, an
79
79
all points where an exception may be thrown are readily apparent visually, this
80
80
also means that we do not have to worry very much about "exception safety".
81
81
82
+
### Exception type upcasting
83
+
84
+
In a language with checked exceptions and subtyping, it is clear that if a function is declared as throwing a particular type, its body should also be able to throw any of its subtypes. Similarly, in a language with structural sum types (a.k.a. anonymous `enum`s, polymorphic variants), one should be able to throw a type with fewer cases in a function declaring that it may throw a superset of those cases. This is essentially what is achieved by the common Rust practice of declaring a custom error `enum` with `From``impl`s for each of the upstream error types which may be propagated:
85
+
86
+
enum MyError {
87
+
IoError(io::Error),
88
+
JsonError(json::Error),
89
+
OtherError(...)
90
+
}
91
+
92
+
impl From<io::Error> for MyError { ... }
93
+
impl From<json::Error> for MyError { ... }
94
+
95
+
Here `io::Error` and `json::Error` can be thought of as subtypes of `MyError`, with a clear and direct embedding into the supertype.
96
+
97
+
The `?` operator should therefore perform such an implicit conversion in the nature of a subtype-to-supertype coercion. The present RFC uses the `std::convert::Into` trait for this purpose (which has a blanket `impl` forwarding from `From`). The precise requirements for a conversion to be "like" a subtyping coercion are an open question; see the "Unresolved questions" section.
98
+
82
99
83
100
## `try`..`catch`
84
101
@@ -183,7 +200,7 @@ are merely one way.
183
200
184
201
match EXPR {
185
202
Ok(a) => a,
186
-
Err(e) => break 'here Err(e)
203
+
Err(e) => break 'here Err(e.into())
187
204
}
188
205
189
206
Where `'here` refers to the innermost enclosing `try` block, or to `'fn` if
@@ -208,7 +225,7 @@ are merely one way.
208
225
'here: {
209
226
Ok(match foo() {
210
227
Ok(a) => a,
211
-
Err(e) => break 'here Err(e)
228
+
Err(e) => break 'here Err(e.into())
212
229
}.bar())
213
230
}
214
231
@@ -238,7 +255,7 @@ are merely one way.
238
255
match 'here: {
239
256
Ok(match foo() {
240
257
Ok(a) => a,
241
-
Err(e) => break 'here Err(e)
258
+
Err(e) => break 'here Err(e.into())
242
259
}.bar())
243
260
} {
244
261
Ok(a) => a,
@@ -263,16 +280,19 @@ a source-to-source translation in this manner, they need not necessarily be
263
280
264
281
Without any attempt at completeness, here are some things which should be true:
(In the above, `foo()` is a function returning any type, and `try_foo()` is a function returning a `Result`.)
272
290
273
291
274
292
# Unresolved questions
275
293
294
+
These questions should be satisfactorally resolved before stabilizing the relevant features, at the latest.
295
+
276
296
## Choice of keywords
277
297
278
298
The RFC to this point uses the keywords `try`..`catch`, but there are a number of other possibilities, each with different advantages and drawbacks:
@@ -302,6 +322,36 @@ Among the considerations:
302
322
* Language-level backwards compatibility when adding new keywords. I'm not sure how this could or should be handled.
303
323
304
324
325
+
## Semantics for "upcasting"
326
+
327
+
What should the contract for a `From`/`Into``impl` be? Are these even the right `trait`s to use for this feature?
328
+
329
+
Two obvious, minimal requirements are:
330
+
331
+
* It should be pure: no side effects, and no observation of side effects. (The result should depend *only* on the argument.)
332
+
333
+
* It should be total: no panics or other divergence, except perhaps in the case of resource exhaustion (OOM, stack overflow).
334
+
335
+
The other requirements for an implicit conversion to be well-behaved in the context of this feature should be thought through with care.
336
+
337
+
Some further thoughts and possibilities on this matter:
338
+
339
+
* It should be "like a coercion from subtype to supertype", as described earlier. The precise meaning of this is not obvious.
340
+
341
+
* A common condition on subtyping coercions is coherence: if you can compound-coerce to go from `A` to `Z` indirectly along multiple different paths, they should all have the same end result.
342
+
343
+
* It should be unambiguous, or preserve the meaning of the input: `impl From<u8> for u32` as `x as u32` feels right; as `(x as u32) * 12345` feels wrong, even though this is perfectly pure, total, and injective. What this means precisely in the general case is unclear.
344
+
345
+
* It should be lossless, or in other words, injective: it should map each observably-different element of the input type to observably-different elements of the output type. (Observably-different means that it is possible to write a program which behaves differently depending on which one it gets, modulo things that "shouldn't count" like observing execution time or resource usage.)
346
+
347
+
* The types converted between should the "same kind of thing": for instance, the *existing*`impl From<u32> for Ipv4Addr` is pretty suspect on this count. (This perhaps ties into the subtyping angle: `Ipv4Addr` is clearly not a supertype of `u32`.)
348
+
349
+
350
+
## Forwards-compatibility
351
+
352
+
If we later want to generalize this feature to other types such as `Option`, as described below, will we be able to do so while maintaining backwards-compatibility?
353
+
354
+
305
355
# Drawbacks
306
356
307
357
* Increases the syntactic surface area of the language.
0 commit comments