- Feature Name:
try_trait
- Start Date: 2017-01-19
- RFC PR: rust-lang/rfcs#1859
- Rust Issue: rust-lang/rust#31436
Summary
IntroduceTry
for customizing the behavior?
operatorResult
.
Motivation
Using ?
with types other than Result結果、戻り値
The ?
operatorResult
, but it really appliestry_opt!
macro confirms, it is common to find similarOption
values and other types. Consider
The overarching goal of this RFC is to allow?
operator:
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?
to Result
and Option
, rather we would like to make something more extensible. For example, futures definedfutures
crate typically
- a successful result;結果、戻り値
- a "not ready yet" value, indicating that the caller呼び出し側should try again later;
- an error.
Code working with futures typicallytry_ready!
macro used in futures. If this 3-state value were written as an enum:
Then one could replace code like try_ready!(self.stream.poll())
with self.stream.poll()?
.
(Currently, the type Poll
in the futures crate is definedenum
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.)
Support interconversion, but with caution
The existing try!
macro and ?
operator?
to a value of type Result<T, E>
, the surrouding function can have some other return type Result<U, F>
, so long as the error types are related by the From
trait (F: From<E>
). The idea is that if an error occurs,F::from(err)
, where err
is the actualBox<Error>
).
In some cases, it would be useful to be able to convertResult<T, HttpError>
to be convertedHttpResponse
(or vice versa). Or, in the futures example given?
to a Poll
value and use that in a function that itself returns a Poll
:
and we might wish to do the same, but in a function returning a Result
:
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?
is used to convertOption
), 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
Detailed design設計(する)
Playground
Note: if you wish to experiment, this Rust playgroud link contains
Desugaring and the Try
trait
The desugaring of the ?
operatorTry
refers
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 definitionTry
. This trait is definedlibcore
in the ops
module; it is also mirrored in std::ops
. The trait Try
is defined
Initial初期 impls
libcore will also define
Result
The Result
type includes an impl as follows:
This impl permits?
operator
Option
The Option
type includes an impl as follows:
Note the use of the Missing
type, which is specificOption
, rather than a generic type like ()
. This is intended to mitigate the risk of accidental Result
conversion.Result<T, Missing>
to Option<T>
. The idea is that if one uses the Missing
type as an error, that indicates an error that can be "handled" by convertingOption
. (This rationale was originally explained in a comment by Aaron Turon.)
The use of a fresh type like Missing
is recommended whenever one implementsTry
for a type that does not have the #[must_use]
attribute (or, more semantically,
Interaction with type inference
Supporting more types with the ?
operator?
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 expressionvec.iter().map(|e| ...).collect()?
, since the behaviorcollect()
function is determinedtry!
macro days, collect()
would have been forced to return a Result<_, _>
-- but ?
leaves it more open.
This implies that callerscollect()
will have to either use try!
, or write an explicit
Another problem (which also occurstry!
) stems from the use of From
to interconvert errors. This implies that 'nested'?
are often insufficiently constrained for inference to make a decision. The problem here is that the nested?
effectively returns something like From::from(From::from(err))
-- but only the starting point (err
) and the final type are constrained. The inner
How We Teach This
Where and how to document文書 it
This RFC proposes extending?
operator,Result
, so as to avoid?
can be overloaded and offer a link to more comprehensive documentation,?
can be appliedOption
and then explain the desugaring and how one goes about implementing one's own impls.
The reference?
operator
One important note is that we should publish guidelines explaining when it is appropriate to introduceoption::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,#[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 directlyCarrer
trait. A better message would consider
Source?
is appliedTry
trait (for any return type), we can give a message like
?
cannot be applied適用するto a value of typeFoo
Return type does not implementTry
, 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 aResult<T, Box<Error>>
in a function that returns()
At this point, we could likely make a suggestion such as "considerResult<(), Box<Error>>
".
Note however that if ?
is used within an impl of a trait method, or within main()
, or in some other context
Errors cannot be interconverted. Finally, if the return type R
does implementTry
, but a value of type R
cannot be constructedOption<T>
, but ?
is appliedResult<T, ()>
), then we can instead report something like this:
?
cannot be applied適用するto aResult<T, Box<Error>>
in a function that returnsOption<T>
This last part can be tricky, because the error can result
- a missing
From
impl, perhaps a mistake; - the impl of
Try
is intentionally limited, as in the case ofOption
.
We could help the user diagnose this, most likely, by offering some labels like the following:
Considercatch
is stabilized, this could be as simple as saying "considercatch
, or changing the return type to ...". In the absence of catch
, we would have to suggest the introductionmatch
block.
Extended
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
}
});
}
Implementation?
were not desugared when lowering
Drawbacks
One drawback of supporting more types is that type inference becomes harder. This is because an expressionx?
no longer implies that the type of x
is Result
.
There is also the risk that resultsoption::Missing
(rather than, say, a generic type like ()
).
Alternatives
The "essentialist" approach
When this RFC was first proposed, the Try
trait looked quite different:
In this version, Try::try()
convertedFoo
to a Bar
in some distinctFoo
to a Baz
).
This was changed to adopt the current "reductionist" approach, in which all values are first interconverted (in a contextfrom_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 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, 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 thisimpl<T,E> Try<Poll<T,E>> for Result<T, E>
, but this impl runs afoul of the orphan rules.
Traits implemented実装する over higher-kinded types
The desire to avoidResult
and Option
seemed to suggest that the Carrier
trait ought to be definedSelf
parameterTry
trait would have to have. If we were to implementTry
on Option
, it would presumably then have kind type -> type
, but we also wish to implementTry
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 encounteredTry
trait to be implementedResult<T,E>
to Result<U,F>
, allowFrom
trait, in particular). Others, like convertingOption<T>
to Option<U>
, allowbool
to bool
) do not allow
What to name the trait
A number of names have been proposed for this trait. The original name was Carrier
, as the implementing type was the "carrier" for an error value. A proposed alternativeQuestionMark
, named after the operator?
. However, the generalAdd
and not Plus
, 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
Unresolved questions
None.