From e9cb3d5fe4acc86112b1376dd90bbb888b332eb4 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Thu, 19 Jan 2017 23:08:35 +0100 Subject: [PATCH 01/10] extend `?` to operate over other types --- text/0000-try-trait.md | 478 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 478 insertions(+) create mode 100644 text/0000-try-trait.md diff --git a/text/0000-try-trait.md b/text/0000-try-trait.md new file mode 100644 index 00000000000..f4d0bb733cc --- /dev/null +++ b/text/0000-try-trait.md @@ -0,0 +1,478 @@ +- Feature Name: (fill me in with a unique ident, my_awesome_feature) +- Start Date: (fill me in with today's date, YYYY-MM-DD) +- RFC PR: (leave this empty) +- Rust Issue: (leave this empty) + +# Summary +[summary]: #summary + +Introduce a trait `Try` for customizing the behavior of the `?` +operator when applied to types other than `Result`. + +# Motivation +[motivation]: #motivation + +### Using `?` with types other than `Result` + +The `?` operator is very useful for working with `Result`, but it +really applies to any sort of short-circuiting computation. As the +existence and popularity of the `try_opt!` macro confirms, it is +common to find similar patterns when working with `Option` values and +other types. Consider these two lines [from rustfmt](https://github.com/rust-lang-nursery/rustfmt/blob/29e89136957b9eedf54255c8059f8a51fbd82a68/src/expr.rs#L294-L295): + +```rust +let lhs_budget = try_opt!(width.checked_sub(prefix.len() + infix.len())); +let rhs_budget = try_opt!(width.checked_sub(suffix.len())); +``` + +The overarching goal of this RFC is to allow lines like those to be +written using the `?` operator: + +```rust +let lhs_budget = width.checked_sub(prefix.len() + infix.len())?; +let rhs_budget = width.checked_sub(suffix.len())?; +``` + +Naturally, this has all the advantages that `?` offered over `try!` to begin with: + +- suffix notation, allowing for more fluent APIs; +- concise, yet noticeable. + +However, there are some tensions to be resolved. We don't want to +hardcode the behavior of `?` to `Result` and `Option`, rather we would +like to make something more extensible. For example, futures defined +using the `futures` crate typically return one of three values: + +- a successful result; +- a "not ready yet" value, indicating that the caller should try again later; +- an error. + +Code working with futures typically wants to proceed only if a +successful result is returned. "Not ready yet" values as well as +errors should be propagated to the caller. This is exemplified by +[the `try_ready!` macro used in futures](https://github.com/alexcrichton/futures-rs/blob/4b027f4ac668e5024baeb51ad7146652df0b4380/src/poll.rs#L6). If +this 3-state value were written as an enum: + +```rust +enum Poll { + Ready(T), + NotReady, + Error(E), +} +``` + +Then one could replace code like `try_ready!(self.stream.poll())` with +`self.stream.poll()?`. + +(Currently, the type `Poll` in the futures crate is defined +differently, but +[alexcrichton indicates](https://github.com/rust-lang/rfcs/issues/1718#issuecomment-273323992) +that in fact the original design *did* use an `enum` like `Poll`, and +it was changed to be more compatible with the existing `try!` macro, +and hence could be changed back to be more in line with this RFC.) + +### Give control over which interconversions are allowed + +While it is desirable to allow `?` to be used with types other than +`Result`, **we don't want to allow arbitrary interconversion**. For +example, if `x` is a value of type `Option`, we do not want to +allow `x?` to be used in a function that return a `Result` or a +`Poll` (from the futures example). Typically, we would only want +`x?` to be used in functions that return `Option` (for some `U`). + +To see why, let's consider the case where `x?` is used in a function +that returns a `Poll`. Consider the case where `x` is `None`, +and hence we want to return early from the enclosing function. This +means we have to create a `Poll` value to return -- but which +variant should we use? It's not clear whether a `None` value +represents `Poll::NotReady` or `Poll::Error` (and, if the latter, it's +not clear what error!). The same applies, in a less clear fashion, to +interoperability between `Option` and `Result` -- it is not +clear whether `Some` or `None` should represent the error state, and +it's better for users to make that interconversion themselves via a +`match` or the `or_err()` method. + +At the same time, there may be some cases where it makes sense to +allow interconversion between types. For example, +[a library might wish to permit a `Result` to be converted into an `HttpResponse`](https://github.com/rust-lang/rfcs/issues/1718#issuecomment-241631468) +(or vice versa). And of course the existing `?` operator allows a +`Result` to be converted into a `Result` so long as `F: +From` (note that the types `T` and `U` are irrelevant here, as we +are only concerned with the error path). The general rule should be +that `?` can be used to interconvert between "semantically equivalent" +types. This notion of semantic equivalent is not something that can be +defined a priori in the language, and hence the design in this RFC +leaves the choice of what sorts of interconversions to enable up to +the end-user. (However, see the unresolved question at the end +concerning interactions with the orphan rules.) + +# Detailed design +[design]: #detailed-design + +### Desugaring and the `Try` trait + +The desugaring of the `?` operator is changed to the following, where +`Try` refers to a new trait that will be introduced shortly: + +```rust +match Try::try(expr) { + Ok(v) => v, + Err(e) => return e, // presuming no `catch` in scope +} +``` + +If a `catch` is in scope, the desugaring is roughly the same, except +that instead of returning, we would break out of the `catch` with `e` +as the error value. + +This definition refers to a trait `Try`. This trait is defined in +`libcore` in the `ops` module; it is also mirrored in `std::ops`. The +trait `Try` is defined as follows: + +```rust +trait Try { + type Success; + + /// Applies the "?" operator. A return of `Ok(t)` means that the + /// execution should continue normally, and the result of `?` is the + /// value `t`. A return of `Err(e)` means that execution should branch + /// to the innermost enclosing `catch`, or return from the function. + /// The value `e` in that case is the result to be returned. + /// + /// Note that the value `t` is the "unwrapped" ok value, whereas the + /// value `e` is the *wrapped* abrupt result. So, for example, if `?` + /// is applied to a `Result`, then the types `T` and `E` + /// here might be `T = i32` and `E = Result<(), Error>`. In + /// particular, note that the `E` type is *not* `Error`. + fn try(self) -> Result; +} +``` + +### Initial impls + +libcore will also define the following impls for the following types. + +**Result** + +The `Result` type includes an impl as follows: + +```rust +impl Try> for Result where F: From { + type Success = T; + + fn try(self) -> Result> { + match self { + Ok(v) => Ok(v), + Err(e) => Err(Err(F::from(e))) + } + } +} +``` + +This impl permits the `?` operator to be used on results in the same +fashion as it is used today. + +**Option** + +The `Option` type includes an impl as follows: + +```rust +impl Try> for Option { + type Success = T; + + fn try(self) -> Result> { + match self { + Some(v) => Ok(v), + None => Err(None), + } + } +} +``` + +**Poll** + +The `Poll` type is not included in the standard library. But just for +completeness, the equivalent of the `try_ready!` macro might be +implemented as follows: + +```rust +impl Try> for Poll where F: From { + type Success = T; + + fn try(self) -> Result> { + match self { + Poll::Ready(v) => Ok(v), + Poll::NotReady => Err(Poll::NotReady), + Poll::Err(e) => Err(Poll::Err(F::from(e))), + } + } +} +``` + +### Interaction with type inference + +Supporting more types with the `?` operator can be somewhat limiting +for type inference. In particular, if `?` only works on values of type +`Result` (as did the old `try!` macro), then `x?` forces the type of +`x` to be `Result`. This can be significant in an expression like +`vec.iter().map(|e| ...).collect()?`, since the behavior of the +`collect()` function is determined by the type it returns. In the old +`try!` macro days, `collect()` would have been forced to return a +`Result<_, _>` -- but `?` leaves it more open. + +However, the impact of this should be limited, thanks to the rule +that disallows arbitrary interconversion. In particular, consider the expression +above in context: + +```rust +fn foo() -> Result { + let v: Vec<_> = vec.iter().map(|e| ...).collect()?; + ... +} +``` + +While it's true that `?` operator can be applied to values of many +different types, in this context it's clear that it must be applied to +a value of **some type that can be converted to a `Result` in +the case of failure**. Since we don't support arbitrary +interconversion, this in fact means that the the only type which +`collect()` could have would be some sort of `Result`. **So we ought +to be able to infer the types in this example without further +annotation.** However, the current implementation fails to do so (as +can be seen in [this example](https://is.gd/v0UrMK)). This appears to +be a limitation of the trait implementation, which should be fixed +separately. (For example, +[switching the role of the type parameters in `Try` resolves the problem](https://is.gd/13A7n0).) +This merits more investigation but need not hold up the RFC. + +# How We Teach This +[how-we-teach-this]: #how-we-teach-this + +This RFC proposes extending an existing operator to permit the same +general short-circuiting pattern to be used with more types. This more +general behavior can be taught at the same time as the `?` operator is +introduced more generally, as part of teaching how best to do error +handling in Rust. + +The reference will have to be updated to include the new trait, +naturally. The Rust book and Rust by example should be expanded to +include coverage of the `?` operator being used on a variety of types. + +One important note is that we should develop and publish guidelines +explaining when it is appropriate to implement a `Try` interconversion +impl and when it is not (i.e., expand on the concept of semantic +equivalence used to justify why we do not permit `Option` to be +interconverted with `Result` and so forth). + +# Drawbacks +[drawbacks]: #drawbacks + +One drawback of supporting more types is that type inference becomes +harder. This is because an expression like `x?` no longer implies that +the type of `x` is `Result`. However, type inference is still expected +to work well in practice, as discussed in the detailed design section. + +# Alternatives +[alternatives]: #alternatives + +### Using an associated type for the success value + +The proposed `Try` trait has one generic type parameter (`E`) which +encodes the type to return on error. The type to return on success is +encoded as an associated type (`type Success`). This implies that the +type of the expression `x?` (i.e., the `Success` type) is going to be +determined by the type of `x` (as well as the return type `E`). An +alternative formulation of the `Try` trait used two generic type +parameters, one for success and one for error: + +```rust +trait Try { + fn try(self) -> Result +} +``` + +In this formulation, the type of `x?` could be influenced by both the +return type `E` as well as the type to which `x?` is being coerced. +This implies that e.g. one could make a version of `?` that uses +`Into` implicitly both on the success *and* the error values: + +```Rust +impl Try> for Result where F: From, V: From { + fn try(self) -> Result> { + match self { + Ok(t) => Ok(V::from(t)), + Err(e) => Err(Err(F::from(e))) + } + } +} +``` + +In general, having this flexibility seemed undesirable, since it would +be surprising for `x?` to perform coercions on the unwrapped value on +the success path, and it would also potentially present an inference +challenge, since the type of `x?` would not necessarily be uniquely +determined by the type of `x`. + +In fact, if we wanted to ensure that the type of `x?` is determined +*solely* by `x` and not by the surrouding return type `E`, we might +also consider introducing two traits: + +```rust +trait Try: TrySuccess { + fn try(self) -> Result; +} + +trait TrySuccess { + type Success; +} +``` + +One would then implement these traits as follows: + +```rust +impl Try> for Option { ... } + +impl TrySuccess for Option { + type Success = T; +} +``` + +Note in particular the second impl, which shows that the type +`Success` is defined purely in terms of the `Option` to which the +`?` is applied, and not the `Option` that it returns in the case of +error. + +### Using a distinct return type + +The `Try` trait uses `Result` as its return type. It has also been proposed +that we could introduce a distinct enum type for the return value. For example: + +```rust +enum TryResult { + Ok(T), + Abrupt(E), +} +``` + +Re-using `Result` was chosen because it is simpler (fewer things being +added). It also allows manual invocations of `Try` (should there by +any, perhaps in macros) to re-use the rich set of methods available on +`Result`. Finally, the `Result` type seems semantically +appropriate. In general, `Result` is used to indicate whether normal +execution can continue (`Ok`) or whether some form of recoverable +error occurred (`Err`). In this case, "normal execution" means the +rest of the function, and the recoverable error means abrupt +termination: + +- returning a `Ok(v)` value means that (a) execution + should continue normally and (b) the result `v` represents the value to + be propagated; +- returning a `Err(e)` value means that (a) execution should return + abruptly and (b) the result `e` is the value to be propagated (note + that `e` may itself be a `Result`, if the function returns + `Result`). + +### The original `Carrier` trait proposal + +The original `Carrier` as proposed in RFC 243 had a rather different +design: + +```rust +trait ResultCarrier { + type Normal; + type Exception; + fn embed_normal(from: Normal) -> Self; + fn embed_exception(from: Exception) -> Self; + fn translate>(from: Self) -> Other; +} +``` + +Whereas this `Try` trait links the type of the value being matched +(`Self`) with the type that the enclosing function returns (`E`), the +`Carrier` was implemented separately for each type (e.g., `Result` and +`Option`). It allowed any kind of carrier to be converted to any other +kind of carrier, which fails to preserve the "semantic equivalent" +property. It would also interact poorly with type inference, as +discussed in the "Detailed Design" section. + +### Traits implemented over higher-kinded types + +The "semantic equivalent" property might suggest that the `Carrier` +trait ought to be defined over higher-kinded types (or generic +associated types) in some form. The most obvious downside of such a +design is that Rust does not offer higher-kinded types nor anything +equivalent to them today, and hence we would have to block on that +design effort. But it also turns out that HKT is +[not a particularly good fit for the problem](https://github.com/rust-lang/rust/pull/35056#issuecomment-240129923). To +start, consider what "kind" the `Self` parameter on the `Try` trait +would have to have. If we were to implement `Try` on `Option`, it +would presumably then have kind `type -> type`, but we also wish to +implement `Try` on `Result`, which has kind `type -> type -> +type`. There has even been talk of implementing `Try` for simple types +like `bool`, which simply have kind `type`. More generally, the +problems encountered are quite similar to the problems that +[Simon Peyton-Jones describes in attempting to model collections using HKT](https://github.com/rust-lang/rust/pull/35056#issuecomment-240129923): +we wish the `Try` trait to be implemented in a great number of +scenarios. Some of them, like converting `Result` to +`Result`, allow for the type of the success value and the error +value to both be changed, though not arbitrarily (subject to the +`From` trait, in particular). Others, like converting `Option` to +`Option`, allow only the type of the success value to change, +whereas others (like converting `bool` to `bool`) do not allow either +type to change. + +### What to name the trait + +A number of names have been proposed for this trait. The original name +was `Carrier`, as the trait "carrier" an error value. A proposed +alternative was `QuestionMark`, named after the operator `?`. However, +the general consensus seemed to be that since Rust operator +overloading traits tend to be named after the *operation* that the +operator performed (e.g., `Add` and not `Plus`, `Deref` and not `Star` +or `Asterix`), it was more appropriate to name the trait `Try`, which +seems to be the best name for the operation in question. + +# Unresolved questions +[unresolved]: #unresolved-questions + +**We need to resolve the interactions with type inference.** It is +important that expressions like `vec.iter().map(|e| ...).collect()?` +are able to infer the type of `collect()` from context. This may +require small tweaks to the `Try` trait, though the author considers +that unlikely. + +**Should we reverse the order of the trait's type parameters?** The +current ordering of the trait type parameters seems natural, since +`Self` refers to the type of the value to which `?` is +applied. However, it has +[negative interactions with the orphan rules](https://github.com/rust-lang/rfcs/issues/1718#issuecomment-273353457). +In particular, it means that one cannot write an impl that converts a +`Result` into any other type. For example, in the futures library, it +would be nice to have an impl that allows a result to be converted +into a `Poll`: + +```rust +impl Try> for Result + where F: From +{ } +``` + +However, this would fall afoul of the current orphan rules. If we +switched the order of the trait's type parameters, then this impl +would be allowed, but of course other impls would not be (e.g., +something which allowed a `Poll` to be converted into a `Result`, +although that particular conversion would not make sense). Certainly +this is a more general problem with the orphan rules that we might +want to consider resolving in a more general way, and indeed +[specialization may provide a solution]. But it may still be worth +considering redefining the trait to sidestep the problem in the short +term. If we chose to do so, the trait would look like: + +```rust +trait Try { + type Success; + fn try(value: V) -> Result; +} +``` + +[specialization may provide a solution]: https://github.com/rust-lang/rfcs/issues/1718#issuecomment-273415458 From 1227d5da66db0ab9eaaaf3c0a10d486c0c1f3ec6 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Fri, 20 Jan 2017 06:27:26 +0100 Subject: [PATCH 02/10] correct two nits --- text/0000-try-trait.md | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/text/0000-try-trait.md b/text/0000-try-trait.md index f4d0bb733cc..ea1ea0e36b4 100644 --- a/text/0000-try-trait.md +++ b/text/0000-try-trait.md @@ -1,5 +1,5 @@ -- Feature Name: (fill me in with a unique ident, my_awesome_feature) -- Start Date: (fill me in with today's date, YYYY-MM-DD) +- Feature Name: `try_trait` +- Start Date: 2017-01-19 - RFC PR: (leave this empty) - Rust Issue: (leave this empty) @@ -424,13 +424,14 @@ type to change. ### What to name the trait A number of names have been proposed for this trait. The original name -was `Carrier`, as the trait "carrier" an error value. A proposed -alternative was `QuestionMark`, named after the operator `?`. However, -the general consensus seemed to be that since Rust operator -overloading traits tend to be named after the *operation* that the -operator performed (e.g., `Add` and not `Plus`, `Deref` and not `Star` -or `Asterix`), it was more appropriate to name the trait `Try`, which -seems to be the best name for the operation in question. +was `Carrier`, as the implementing type was the "carrier" for an error +value. A proposed alternative was `QuestionMark`, named after the +operator `?`. However, the general consensus seemed to be that since +Rust operator overloading traits tend to be named after the +*operation* that the operator performed (e.g., `Add` and not `Plus`, +`Deref` and not `Star` or `Asterix`), it was more appropriate to name +the trait `Try`, which seems to be the best name for the operation in +question. # Unresolved questions [unresolved]: #unresolved-questions From 13f68f45a529aa4f79df12bfba503c4d317a2077 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Fri, 20 Jan 2017 06:41:51 +0100 Subject: [PATCH 03/10] adjust documentation --- text/0000-try-trait.md | 40 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/text/0000-try-trait.md b/text/0000-try-trait.md index ea1ea0e36b4..19863dbdf6d 100644 --- a/text/0000-try-trait.md +++ b/text/0000-try-trait.md @@ -248,11 +248,16 @@ This merits more investigation but need not hold up the RFC. # How We Teach This [how-we-teach-this]: #how-we-teach-this +### Where and how to document it + This RFC proposes extending an existing operator to permit the same -general short-circuiting pattern to be used with more types. This more -general behavior can be taught at the same time as the `?` operator is -introduced more generally, as part of teaching how best to do error -handling in Rust. +general short-circuiting pattern to be used with more types. When +initially teaching the `?` operator, it would probably be best to +stick to examples around `Result`, so as to avoid confusing the +issue. However, at that time we can also mention that `?` can be +overloaded and offer a link to more comprehensive documentation, which +would show how `?` can be applied to `Option` and then explain the +desugaring and how one goes about implementing one's own impls. The reference will have to be updated to include the new trait, naturally. The Rust book and Rust by example should be expanded to @@ -264,6 +269,33 @@ impl and when it is not (i.e., expand on the concept of semantic equivalence used to justify why we do not permit `Option` to be interconverted with `Result` and so forth). +### Error messages + +Another important factor is the error message when `?` is used in a +function whose return type is not suitable. The current error message +in this scenario is quite opaque and directly references the `Carrer` +trait. A better message would be contingent on whether `?` is applied +to a value that implements the `Try` trait (for any return type). If +not, we can give a message like + +> `?` cannot be applied to a value of type `Foo` + +If, however, `?` *can* be applied to this type, but not with a function +of the given return type, we can instead report something like this: + +> cannot use the `?` operator in a function that returns `()` + +or perhaps if we want to be more strictly correct: + +> `?` cannot be applied to a `io::Result` in a function that returns `()` + +We could also go further and analyze the type to which `?` is applied +and figure out the set of legal return types for the function to +have. So if the code is invoking `foo.write()?` (i.e., applying `?` to an +`io::Result`), then we could offer a suggestion like "consider changing +the return type to `Result<(), io::Error>`" or perhaps just "consider +changing the return type to a `Result"`. + # Drawbacks [drawbacks]: #drawbacks From 28cc82277646eaa6028b7847165dfddbbcfe59a0 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Fri, 20 Jan 2017 06:48:59 +0100 Subject: [PATCH 04/10] extend with a side note covering the implementation --- text/0000-try-trait.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/text/0000-try-trait.md b/text/0000-try-trait.md index 19863dbdf6d..f6a799e822a 100644 --- a/text/0000-try-trait.md +++ b/text/0000-try-trait.md @@ -296,6 +296,11 @@ have. So if the code is invoking `foo.write()?` (i.e., applying `?` to an the return type to `Result<(), io::Error>`" or perhaps just "consider changing the return type to a `Result"`. +On an implementation note, it would probably be helpful for improving +the error message if `?` were not desugared when lowering from AST to +HIR but rather when lowering from HIR to MIR. This would also make it +easier to implement `catch`. + # Drawbacks [drawbacks]: #drawbacks From 9af60a17ecfc1b5d488efc60bd3dae4c3b127ac3 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Sat, 21 Jan 2017 00:05:22 +0100 Subject: [PATCH 05/10] add text about not being able to change return type --- text/0000-try-trait.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/text/0000-try-trait.md b/text/0000-try-trait.md index f6a799e822a..5be184e2940 100644 --- a/text/0000-try-trait.md +++ b/text/0000-try-trait.md @@ -291,10 +291,15 @@ or perhaps if we want to be more strictly correct: We could also go further and analyze the type to which `?` is applied and figure out the set of legal return types for the function to -have. So if the code is invoking `foo.write()?` (i.e., applying `?` to an -`io::Result`), then we could offer a suggestion like "consider changing -the return type to `Result<(), io::Error>`" or perhaps just "consider -changing the return type to a `Result"`. +have. So if the code is invoking `foo.write()?` (i.e., applying `?` to +an `io::Result`), then we could offer a suggestion like "consider +changing the return type to `Result<(), io::Error>`" or perhaps just +"consider changing the return type to a `Result"`. Note however that +if `?` is used within an impl of a trait method, or within `main()`, +or in some other context where the user is not free to change the type +signature, then we should make this suggestion. In the case of an impl +of a trait defined in the current crate, we could consider suggesting +that the user change the definition of the trait. On an implementation note, it would probably be helpful for improving the error message if `?` were not desugared when lowering from AST to From f89568b1fe5db4d01c4668e0d334d4a5abb023d8 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Sat, 21 Jan 2017 05:36:33 +0100 Subject: [PATCH 06/10] add further notes to the doc section --- text/0000-try-trait.md | 44 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/text/0000-try-trait.md b/text/0000-try-trait.md index 5be184e2940..d28d7a1a824 100644 --- a/text/0000-try-trait.md +++ b/text/0000-try-trait.md @@ -294,12 +294,44 @@ and figure out the set of legal return types for the function to have. So if the code is invoking `foo.write()?` (i.e., applying `?` to an `io::Result`), then we could offer a suggestion like "consider changing the return type to `Result<(), io::Error>`" or perhaps just -"consider changing the return type to a `Result"`. Note however that -if `?` is used within an impl of a trait method, or within `main()`, -or in some other context where the user is not free to change the type -signature, then we should make this suggestion. In the case of an impl -of a trait defined in the current crate, we could consider suggesting -that the user change the definition of the trait. +"consider changing the return type to a `Result"`. + +Note however that if `?` is used within an impl of a trait method, or +within `main()`, or in some other context where the user is not free +to change the type signature, then we should not make this +suggestion. In the case of an impl of a trait defined in the current +crate, we could consider suggesting that the user change the +definition of the trait. + +Especially in contexts where the return type cannot be changed, but +possibly in other contexts as well, it would make sense to advise the +user about how they can catch an error instead, if they chose. Once +`catch` is implemented, this could be as simple as saying "consider +introducing a `catch`, or changing the return type to ...". In the +absence of `catch`, we would have to suggest the introduction of a +`match` block. + +In the extended error message, for those cases where the return type +cannot easily be changed, we might consider suggesting that the +fallible portion of the code is refactored into a helper function, thus +roughly following this pattern: + +```rust +fn inner_main() -> Result<(), HLError> { + let args = parse_cmdline()?; + // all the real work here +} + +fn main() { + process::exit(match inner_main() { + Ok(_) => 0, + Err(ref e) => { + writeln!(io::stderr(), "{}", e).unwrap(); + 1 + } + }); +} +``` On an implementation note, it would probably be helpful for improving the error message if `?` were not desugared when lowering from AST to From 3ddcfa6b7ba368d448252eab1d3928c76bf7a622 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Fri, 21 Apr 2017 06:29:54 -0400 Subject: [PATCH 07/10] adapt RFC text to the reductionist approach --- text/0000-try-trait.md | 522 ++++++++++++++++++----------------------- 1 file changed, 225 insertions(+), 297 deletions(-) diff --git a/text/0000-try-trait.md b/text/0000-try-trait.md index d28d7a1a824..49b6f4e1ccb 100644 --- a/text/0000-try-trait.md +++ b/text/0000-try-trait.md @@ -71,40 +71,47 @@ that in fact the original design *did* use an `enum` like `Poll`, and it was changed to be more compatible with the existing `try!` macro, and hence could be changed back to be more in line with this RFC.) -### Give control over which interconversions are allowed - -While it is desirable to allow `?` to be used with types other than -`Result`, **we don't want to allow arbitrary interconversion**. For -example, if `x` is a value of type `Option`, we do not want to -allow `x?` to be used in a function that return a `Result` or a -`Poll` (from the futures example). Typically, we would only want -`x?` to be used in functions that return `Option` (for some `U`). - -To see why, let's consider the case where `x?` is used in a function -that returns a `Poll`. Consider the case where `x` is `None`, -and hence we want to return early from the enclosing function. This -means we have to create a `Poll` value to return -- but which -variant should we use? It's not clear whether a `None` value -represents `Poll::NotReady` or `Poll::Error` (and, if the latter, it's -not clear what error!). The same applies, in a less clear fashion, to -interoperability between `Option` and `Result` -- it is not -clear whether `Some` or `None` should represent the error state, and -it's better for users to make that interconversion themselves via a -`match` or the `or_err()` method. - -At the same time, there may be some cases where it makes sense to -allow interconversion between types. For example, +### Support interconversion, but with caution + +The existing `try!` macro and `?` operator already allow a limit +amount of type conversion, specifically in the error case. That is, if +you apply `?` to a value of type `Result`, the surrouding +function can have some other return type `Result`, so long as +the error types are related by the `From` trait (`F: From`). The +idea is that if an error occurs, we will wind up returning +`F::from(err)`, where `err` is the actual error. This is used (for +example) to "upcast" various errors that can occur in a function into +a common error type (e.g., `Box`). + +In some cases, it would be useful to be able to convert even more +freely. At the same time, there may be some cases where it makes sense +to allow interconversion between types. For example, [a library might wish to permit a `Result` to be converted into an `HttpResponse`](https://github.com/rust-lang/rfcs/issues/1718#issuecomment-241631468) -(or vice versa). And of course the existing `?` operator allows a -`Result` to be converted into a `Result` so long as `F: -From` (note that the types `T` and `U` are irrelevant here, as we -are only concerned with the error path). The general rule should be -that `?` can be used to interconvert between "semantically equivalent" -types. This notion of semantic equivalent is not something that can be -defined a priori in the language, and hence the design in this RFC -leaves the choice of what sorts of interconversions to enable up to -the end-user. (However, see the unresolved question at the end -concerning interactions with the orphan rules.) +(or vice versa). Or, in the futures example given above, we might wish +to apply `?` to a `Poll` value and use that in a function that itself +returns a `Poll`: + +```rust +fn foo() -> Poll { + let x = bar()?; // propogate error case +} +``` + +and we might wish to do the same, but in a function returning a `Result`: + +```rust +fn foo() -> Result { + let x = bar()?; // propogate error case +} +``` + +However, we wish to be sure that this sort of interconversion is +*intentional*. In particular, `Result` is often used with a semantic +intent to mean an "unhandled error", and thus if `?` is used to +convert an error case into a "non-error" type (e.g., `Option`), there +is a risk that users accidentally overlook error cases. To mitigate +this risk, we adopt certain conventions (see below) in that case to +help ensure that "accidental" interconversion does not occur. # Detailed design [design]: #detailed-design @@ -115,9 +122,12 @@ The desugaring of the `?` operator is changed to the following, where `Try` refers to a new trait that will be introduced shortly: ```rust -match Try::try(expr) { +match Try::into_result(expr) { Ok(v) => v, - Err(e) => return e, // presuming no `catch` in scope + + // here, the `return` presumes that there is + // no `catch` in scope: + Err(e) => return Try::from_error(From::from(e)), } ``` @@ -130,21 +140,32 @@ This definition refers to a trait `Try`. This trait is defined in trait `Try` is defined as follows: ```rust -trait Try { - type Success; - +trait Try { + type Ok; + type Error; + /// Applies the "?" operator. A return of `Ok(t)` means that the /// execution should continue normally, and the result of `?` is the /// value `t`. A return of `Err(e)` means that execution should branch /// to the innermost enclosing `catch`, or return from the function. - /// The value `e` in that case is the result to be returned. /// - /// Note that the value `t` is the "unwrapped" ok value, whereas the - /// value `e` is the *wrapped* abrupt result. So, for example, if `?` - /// is applied to a `Result`, then the types `T` and `E` - /// here might be `T = i32` and `E = Result<(), Error>`. In - /// particular, note that the `E` type is *not* `Error`. - fn try(self) -> Result; + /// If an `Err(e)` result is returned, the value `e` will be "wrapped" + /// in the return type of the enclosing scope (which must itself implement + /// `Try`). Specifically, the value `X::from_error(From::from(e))` + /// is returned, where `X` is the return type of the enclosing function. + fn into_result(self) -> Result; + + /// Wrap an error value to construct the composite result. For example, + /// `Result::Err(x)` and `Result::from_error(x)` are equivalent. + fn from_error(v: Self::Error) -> Self; + + /// Wrap an OK value to construct the composite result. For example, + /// `Result::Ok(x)` and `Result::from_ok(x)` are equivalent. + /// + /// *The following function has an anticipated use, but is not used + /// in this RFC. It is included because we would not want to stabilize + /// the trait without including it.* + fn from_ok(v: Self::Ok) -> Self; } ``` @@ -157,14 +178,20 @@ libcore will also define the following impls for the following types. The `Result` type includes an impl as follows: ```rust -impl Try> for Result where F: From { - type Success = T; +impl Try for Result { + type Ok = T; + type Error = E; - fn try(self) -> Result> { - match self { - Ok(v) => Ok(v), - Err(e) => Err(Err(F::from(e))) - } + fn into_result(self) -> Self { + self + } + + fn from_ok(v: T) -> Self { + Ok(v) + } + + fn from_error(v: T) -> Self { + Err(v) } } ``` @@ -177,38 +204,40 @@ fashion as it is used today. The `Option` type includes an impl as follows: ```rust -impl Try> for Option { - type Success = T; - - fn try(self) -> Result> { - match self { - Some(v) => Ok(v), - None => Err(None), - } - } -} -``` +mod option { + pub struct Missing; -**Poll** + impl Try for Option { + type Ok = T; + type Error = Missing; -The `Poll` type is not included in the standard library. But just for -completeness, the equivalent of the `try_ready!` macro might be -implemented as follows: + fn into_result(self) -> Self { + self.ok_or(Missing) + } + + fn from_ok(v: T) -> Self { + Some(v) + } -```rust -impl Try> for Poll where F: From { - type Success = T; - - fn try(self) -> Result> { - match self { - Poll::Ready(v) => Ok(v), - Poll::NotReady => Err(Poll::NotReady), - Poll::Err(e) => Err(Poll::Err(F::from(e))), + fn from_error(_: Missing) -> Self { + None } } -} +} ``` +Note the use of the `Missing` type. This is intended to mitigate the +risk of accidental `Result -> Option` conversion. In particular, we +will only allow conversion from `Result` to `Option`. +The idea is that if one uses the `Missing` type as an error, that +indicates an error that can be "handled" by converting the value into +an `Option`. + +The use of a fresh type like `Missing` is recommended whenever one +implements `Try` for a type that does not have the `#[must_use]` +attribute (or, more semantically, that does not represent an +"unhandled error"). + ### Interaction with type inference Supporting more types with the `?` operator can be somewhat limiting @@ -220,30 +249,22 @@ for type inference. In particular, if `?` only works on values of type `try!` macro days, `collect()` would have been forced to return a `Result<_, _>` -- but `?` leaves it more open. -However, the impact of this should be limited, thanks to the rule -that disallows arbitrary interconversion. In particular, consider the expression -above in context: +This implies that callers of `collect()` will have to either use +`try!`, or write an explicit type annotation, something like this: ```rust -fn foo() -> Result { - let v: Vec<_> = vec.iter().map(|e| ...).collect()?; - ... -} +vec.iter().map(|e| ...).collect::>()? ``` -While it's true that `?` operator can be applied to values of many -different types, in this context it's clear that it must be applied to -a value of **some type that can be converted to a `Result` in -the case of failure**. Since we don't support arbitrary -interconversion, this in fact means that the the only type which -`collect()` could have would be some sort of `Result`. **So we ought -to be able to infer the types in this example without further -annotation.** However, the current implementation fails to do so (as -can be seen in [this example](https://is.gd/v0UrMK)). This appears to -be a limitation of the trait implementation, which should be fixed -separately. (For example, -[switching the role of the type parameters in `Try` resolves the problem](https://is.gd/13A7n0).) -This merits more investigation but need not hold up the RFC. +Another problem (which also occurs with `try!`) stems from the use of +`From` to interconvert errors. This implies that 'nested' uses of `?` +are +[often insufficiently constrained for inference to make a decision](https://internals.rust-lang.org/t/pre-rfc-fold-ok-is-composable-internal-iteration/4434/23). +The problem here is that the nested use of `?` effectively returns +something like `From::from(From::from(err))` -- but only the starting +point (`err`) and the final type are constrained. The inner type is +not. It's unclear how to address this problem without introducing +some form of inference fallback, which seems orthogonal from this RFC. # How We Teach This [how-we-teach-this]: #how-we-teach-this @@ -263,58 +284,86 @@ The reference will have to be updated to include the new trait, naturally. The Rust book and Rust by example should be expanded to include coverage of the `?` operator being used on a variety of types. -One important note is that we should develop and publish guidelines -explaining when it is appropriate to implement a `Try` interconversion -impl and when it is not (i.e., expand on the concept of semantic -equivalence used to justify why we do not permit `Option` to be -interconverted with `Result` and so forth). +One important note is that we should publish guidelines explaining +when it is appropriate to introduce a special error type (analogous to +the `option::Missing` type included in this RFC) for use with `?`. As +expressed earlier, the rule of thumb ought to be that a special error +type should be used whenever implementing `Try` for a type that does +not, semantically, indicates an unhandled error (i.e., a type for +which the `#[must_use]` attribute would be inappropriate). ### Error messages Another important factor is the error message when `?` is used in a function whose return type is not suitable. The current error message in this scenario is quite opaque and directly references the `Carrer` -trait. A better message would be contingent on whether `?` is applied -to a value that implements the `Try` trait (for any return type). If -not, we can give a message like +trait. A better message would consider various possible cases. + +**Source type does not implement Try.** If `?` is applied to a value +that does not implement the `Try` trait (for any return type), we can +give a message like > `?` cannot be applied to a value of type `Foo` -If, however, `?` *can* be applied to this type, but not with a function -of the given return type, we can instead report something like this: +**Return type does not implement Try.** Otherwise, if the return type +of the function does not implement `Try`, then we can report something +like this (in this case, assuming a fn that returns `()`): > cannot use the `?` operator in a function that returns `()` or perhaps if we want to be more strictly correct: -> `?` cannot be applied to a `io::Result` in a function that returns `()` +> `?` cannot be applied to a `Result>` in a function that returns `()` -We could also go further and analyze the type to which `?` is applied -and figure out the set of legal return types for the function to -have. So if the code is invoking `foo.write()?` (i.e., applying `?` to -an `io::Result`), then we could offer a suggestion like "consider -changing the return type to `Result<(), io::Error>`" or perhaps just -"consider changing the return type to a `Result"`. +At this point, we could likely make a suggestion such as "consider +changing the return type to `Result<(), Box>`". Note however that if `?` is used within an impl of a trait method, or within `main()`, or in some other context where the user is not free -to change the type signature, then we should not make this -suggestion. In the case of an impl of a trait defined in the current -crate, we could consider suggesting that the user change the -definition of the trait. - -Especially in contexts where the return type cannot be changed, but -possibly in other contexts as well, it would make sense to advise the -user about how they can catch an error instead, if they chose. Once -`catch` is implemented, this could be as simple as saying "consider -introducing a `catch`, or changing the return type to ...". In the -absence of `catch`, we would have to suggest the introduction of a -`match` block. - -In the extended error message, for those cases where the return type -cannot easily be changed, we might consider suggesting that the -fallible portion of the code is refactored into a helper function, thus -roughly following this pattern: +to change the type signature (modulo +[RFC 1937](https://github.com/rust-lang/rfcs/pull/1937)), then we +should not make this suggestion. In the case of an impl of a trait +defined in the current crate, we could consider suggesting that the +user change the definition of the trait. + +**Errors cannot be interconverted.** Finally, if the return type `R` +does implement `Try`, but a value of type `R` cannot be constructed +from the resulting error (e.g., the function returns `Option`, but +`?` is applied to a `Result`), then we can instead report +something like this: + +> `?` cannot be applied to a `Result>` in a function that returns `Option` + +This last part can be tricky, because the error can result for one of +two reasons: + +- a missing `From` impl, perhaps a mistake; +- the impl of `Try` is intentionally limited, as in the case of `Option`. + +We could help the user diagnose this, most likely, by offering some labels +like the following: + +```rust +22 | fn foo(...) -> Option { + | --------- requires an error of type `option::Missing` + | write!(foo, ...)?; + | ^^^^^^^^^^^^^^^^^ produces an error of type `io::Error` + | } +``` + +**Consider suggesting the use of catch.** Especially in contexts +where the return type cannot be changed, but possibly in other +contexts as well, it would make sense to advise the user about how +they can catch an error instead, if they chose. Once `catch` is +stabilized, this could be as simple as saying "consider introducing a +`catch`, or changing the return type to ...". In the absence of +`catch`, we would have to suggest the introduction of a `match` block. + +**Extended error message text.** In the extended error message, for +those cases where the return type cannot easily be changed, we might +consider suggesting that the fallible portion of the code is +refactored into a helper function, thus roughly following this +pattern: ```rust fn inner_main() -> Result<(), HLError> { @@ -333,150 +382,69 @@ fn main() { } ``` -On an implementation note, it would probably be helpful for improving -the error message if `?` were not desugared when lowering from AST to -HIR but rather when lowering from HIR to MIR. This would also make it -easier to implement `catch`. +**Implementation note:** it may be helpful for improving the error +message if `?` were not desugared when lowering from AST to HIR but +rather when lowering from HIR to MIR; however, the use of source +annotations may suffice. # Drawbacks [drawbacks]: #drawbacks One drawback of supporting more types is that type inference becomes harder. This is because an expression like `x?` no longer implies that -the type of `x` is `Result`. However, type inference is still expected -to work well in practice, as discussed in the detailed design section. +the type of `x` is `Result`. + +There is also the risk that results or other "must use" values are +accidentally converted into other types. This is mitigated by the use +of newtypes like `option::Missing`. # Alternatives [alternatives]: #alternatives -### Using an associated type for the success value +### The "essentialist" approach -The proposed `Try` trait has one generic type parameter (`E`) which -encodes the type to return on error. The type to return on success is -encoded as an associated type (`type Success`). This implies that the -type of the expression `x?` (i.e., the `Success` type) is going to be -determined by the type of `x` (as well as the return type `E`). An -alternative formulation of the `Try` trait used two generic type -parameters, one for success and one for error: +When this RFC was first proposed, the `Try` trait looked quite different: ```rust -trait Try { - fn try(self) -> Result -} -``` - -In this formulation, the type of `x?` could be influenced by both the -return type `E` as well as the type to which `x?` is being coerced. -This implies that e.g. one could make a version of `?` that uses -`Into` implicitly both on the success *and* the error values: - -```Rust -impl Try> for Result where F: From, V: From { - fn try(self) -> Result> { - match self { - Ok(t) => Ok(V::from(t)), - Err(e) => Err(Err(F::from(e))) - } - } -} -``` - -In general, having this flexibility seemed undesirable, since it would -be surprising for `x?` to perform coercions on the unwrapped value on -the success path, and it would also potentially present an inference -challenge, since the type of `x?` would not necessarily be uniquely -determined by the type of `x`. - -In fact, if we wanted to ensure that the type of `x?` is determined -*solely* by `x` and not by the surrouding return type `E`, we might -also consider introducing two traits: - -```rust -trait Try: TrySuccess { - fn try(self) -> Result; -} - -trait TrySuccess { +trait Try { type Success; -} -``` - -One would then implement these traits as follows: - -```rust -impl Try> for Option { ... } - -impl TrySuccess for Option { - type Success = T; -} -``` - -Note in particular the second impl, which shows that the type -`Success` is defined purely in terms of the `Option` to which the -`?` is applied, and not the `Option` that it returns in the case of -error. - -### Using a distinct return type - -The `Try` trait uses `Result` as its return type. It has also been proposed -that we could introduce a distinct enum type for the return value. For example: - -```rust -enum TryResult { - Ok(T), - Abrupt(E), -} -``` - -Re-using `Result` was chosen because it is simpler (fewer things being -added). It also allows manual invocations of `Try` (should there by -any, perhaps in macros) to re-use the rich set of methods available on -`Result`. Finally, the `Result` type seems semantically -appropriate. In general, `Result` is used to indicate whether normal -execution can continue (`Ok`) or whether some form of recoverable -error occurred (`Err`). In this case, "normal execution" means the -rest of the function, and the recoverable error means abrupt -termination: - -- returning a `Ok(v)` value means that (a) execution - should continue normally and (b) the result `v` represents the value to - be propagated; -- returning a `Err(e)` value means that (a) execution should return - abruptly and (b) the result `e` is the value to be propagated (note - that `e` may itself be a `Result`, if the function returns - `Result`). - -### The original `Carrier` trait proposal - -The original `Carrier` as proposed in RFC 243 had a rather different -design: - -```rust -trait ResultCarrier { - type Normal; - type Exception; - fn embed_normal(from: Normal) -> Self; - fn embed_exception(from: Exception) -> Self; - fn translate>(from: Self) -> Other; -} + fn try(self) -> Result; +} ``` -Whereas this `Try` trait links the type of the value being matched -(`Self`) with the type that the enclosing function returns (`E`), the -`Carrier` was implemented separately for each type (e.g., `Result` and -`Option`). It allowed any kind of carrier to be converted to any other -kind of carrier, which fails to preserve the "semantic equivalent" -property. It would also interact poorly with type inference, as -discussed in the "Detailed Design" section. +In this version, `Try::try()` converted either to an unwrapped +"success" value, or to a error value to be propagated. This allowed +the conversion to take into account the context (i.e., one might +interconvert from a `Foo` to a `Bar` in some distinct way as one +interconverts from a `Foo` to a `Baz`). + +This was changed to adopt the current "reductionist" approach, in +which all values are *first* interconverted (in a context independent +way) to an OK/Error value, and then interconverted again to match the +context using `from_error`. The reasons for the change are roughly as follows: + +- The resulting trait feels simpler and more straight-forward. It also + supports `from_ok` in a simple fashion. +- Context dependent behavior has the potential to be quite surprising. +- The use of types like `option::Missing` mitigates the primary concern that + motivated the original design (avoiding overly loose interconversion). +- It is nice that the use of the `From` trait is now part of the `?` desugaring, + and hence supported universally across all types. +- The interaction with the orphan rules is made somewhat nicer. For example, + using the essentialist alternative, one might like to have a trait + that permits a `Result` to be returned in a function that yields `Poll`. + That would require an impl like this `impl Try> for Result`, + but this impl runs afoul of the orphan rules. ### Traits implemented over higher-kinded types -The "semantic equivalent" property might suggest that the `Carrier` -trait ought to be defined over higher-kinded types (or generic -associated types) in some form. The most obvious downside of such a -design is that Rust does not offer higher-kinded types nor anything -equivalent to them today, and hence we would have to block on that -design effort. But it also turns out that HKT is +The desire to avoid "free interconversion" between `Result` and +`Option` seemed to suggest that the `Carrier` trait ought to be +defined over higher-kinded types (or generic associated types) in some +form. The most obvious downside of such a design is that Rust does not +offer higher-kinded types nor anything equivalent to them today, and +hence we would have to block on that design effort. But it also turns +out that HKT is [not a particularly good fit for the problem](https://github.com/rust-lang/rust/pull/35056#issuecomment-240129923). To start, consider what "kind" the `Self` parameter on the `Try` trait would have to have. If we were to implement `Try` on `Option`, it @@ -510,44 +478,4 @@ question. # Unresolved questions [unresolved]: #unresolved-questions -**We need to resolve the interactions with type inference.** It is -important that expressions like `vec.iter().map(|e| ...).collect()?` -are able to infer the type of `collect()` from context. This may -require small tweaks to the `Try` trait, though the author considers -that unlikely. - -**Should we reverse the order of the trait's type parameters?** The -current ordering of the trait type parameters seems natural, since -`Self` refers to the type of the value to which `?` is -applied. However, it has -[negative interactions with the orphan rules](https://github.com/rust-lang/rfcs/issues/1718#issuecomment-273353457). -In particular, it means that one cannot write an impl that converts a -`Result` into any other type. For example, in the futures library, it -would be nice to have an impl that allows a result to be converted -into a `Poll`: - -```rust -impl Try> for Result - where F: From -{ } -``` - -However, this would fall afoul of the current orphan rules. If we -switched the order of the trait's type parameters, then this impl -would be allowed, but of course other impls would not be (e.g., -something which allowed a `Poll` to be converted into a `Result`, -although that particular conversion would not make sense). Certainly -this is a more general problem with the orphan rules that we might -want to consider resolving in a more general way, and indeed -[specialization may provide a solution]. But it may still be worth -considering redefining the trait to sidestep the problem in the short -term. If we chose to do so, the trait would look like: - -```rust -trait Try { - type Success; - fn try(value: V) -> Result; -} -``` - -[specialization may provide a solution]: https://github.com/rust-lang/rfcs/issues/1718#issuecomment-273415458 +None. From cd6fcb75a30592a486ac719b6bf4e7a0b86b963a Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Fri, 21 Apr 2017 10:46:53 -0400 Subject: [PATCH 08/10] correct typo, add link to playground --- text/0000-try-trait.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/text/0000-try-trait.md b/text/0000-try-trait.md index 49b6f4e1ccb..c3e95e58906 100644 --- a/text/0000-try-trait.md +++ b/text/0000-try-trait.md @@ -116,6 +116,12 @@ help ensure that "accidental" interconversion does not occur. # Detailed design [design]: #detailed-design +### Playground + +Note: if you wish to experiment, +[this Rust playgroud link](https://play.rust-lang.org/?gist=9ef8effa0c1c81bc8bb8dccb07505c54&version=stable&backtrace=0) +contains the traits and impls defined herein. + ### Desugaring and the `Try` trait The desugaring of the `?` operator is changed to the following, where @@ -190,7 +196,7 @@ impl Try for Result { Ok(v) } - fn from_error(v: T) -> Self { + fn from_error(v: E) -> Self { Err(v) } } @@ -211,7 +217,7 @@ mod option { type Ok = T; type Error = Missing; - fn into_result(self) -> Self { + fn into_result(self) -> Result { self.ok_or(Missing) } From 7e264aacd907f1947df7ccf9bca198a6c34d0137 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Thu, 4 May 2017 18:58:35 -0400 Subject: [PATCH 09/10] a few edits --- text/0000-try-trait.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/text/0000-try-trait.md b/text/0000-try-trait.md index c3e95e58906..fbf71e533af 100644 --- a/text/0000-try-trait.md +++ b/text/0000-try-trait.md @@ -232,12 +232,14 @@ mod option { } ``` -Note the use of the `Missing` type. This is intended to mitigate the +Note the use of the `Missing` type, which is specific to `Option`, +rather than a generic type like `()`. This is intended to mitigate the risk of accidental `Result -> Option` conversion. In particular, we will only allow conversion from `Result` to `Option`. The idea is that if one uses the `Missing` type as an error, that indicates an error that can be "handled" by converting the value into -an `Option`. +an `Option`. (This rationale was originally +[explained in a comment by Aaron Turon](https://github.com/rust-lang/rfcs/pull/1859#issuecomment-282091865).) The use of a fresh type like `Missing` is recommended whenever one implements `Try` for a type that does not have the `#[must_use]` @@ -402,7 +404,8 @@ the type of `x` is `Result`. There is also the risk that results or other "must use" values are accidentally converted into other types. This is mitigated by the use -of newtypes like `option::Missing`. +of newtypes like `option::Missing` (rather than, say, a generic type +like `()`). # Alternatives [alternatives]: #alternatives @@ -432,8 +435,9 @@ context using `from_error`. The reasons for the change are roughly as follows: - The resulting trait feels simpler and more straight-forward. It also supports `from_ok` in a simple fashion. - Context dependent behavior has the potential to be quite surprising. -- The use of types like `option::Missing` mitigates the primary concern that - motivated the original design (avoiding overly loose interconversion). +- The use of specific types like `option::Missing` mitigates the + primary concern that motivated the original design (avoiding overly + loose interconversion). - It is nice that the use of the `From` trait is now part of the `?` desugaring, and hence supported universally across all types. - The interaction with the orphan rules is made somewhat nicer. For example, @@ -459,7 +463,7 @@ implement `Try` on `Result`, which has kind `type -> type -> type`. There has even been talk of implementing `Try` for simple types like `bool`, which simply have kind `type`. More generally, the problems encountered are quite similar to the problems that -[Simon Peyton-Jones describes in attempting to model collections using HKT](https://github.com/rust-lang/rust/pull/35056#issuecomment-240129923): +[Simon Peyton-Jones describes in attempting to model collections using HKT](https://www.microsoft.com/en-us/research/wp-content/uploads/1997/01/multi.pdf): we wish the `Try` trait to be implemented in a great number of scenarios. Some of them, like converting `Result` to `Result`, allow for the type of the success value and the error @@ -477,7 +481,7 @@ value. A proposed alternative was `QuestionMark`, named after the operator `?`. However, the general consensus seemed to be that since Rust operator overloading traits tend to be named after the *operation* that the operator performed (e.g., `Add` and not `Plus`, -`Deref` and not `Star` or `Asterix`), it was more appropriate to name +`Deref` and not `Star` or `Asterisk`), it was more appropriate to name the trait `Try`, which seems to be the best name for the operation in question. From 80db2d9ad12fd21c9d04a7a39e46df892a3e9d27 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Tue, 30 May 2017 12:58:54 -0400 Subject: [PATCH 10/10] fix typos --- text/0000-try-trait.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/0000-try-trait.md b/text/0000-try-trait.md index fbf71e533af..27c75bcc0de 100644 --- a/text/0000-try-trait.md +++ b/text/0000-try-trait.md @@ -93,7 +93,7 @@ returns a `Poll`: ```rust fn foo() -> Poll { - let x = bar()?; // propogate error case + let x = bar()?; // propagate error case } ``` @@ -101,7 +101,7 @@ and we might wish to do the same, but in a function returning a `Result`: ```rust fn foo() -> Result { - let x = bar()?; // propogate error case + let x = bar()?; // propagate error case } ```