Summary

Introduce

導入する
a trait Try for customizing the behavior
ふるまい
of the ? operator
演算子
when applied
適用する
to types other than Result
結果、戻り値
.

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:

#![allow(unused)] fn main() { 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:
演算子

#![allow(unused)] fn main() { 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. If this 3-state value were written as an enum:

#![allow(unused)] fn main() { enum Poll<T, E> { 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 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.)

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<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,
起こる
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<Error>).

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<T, HttpError> to be converted
変換する
into an HttpResponse
(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:

#![allow(unused)] fn main() { fn foo() -> Poll<T, E> { let x = bar()?; // propagate error case } }

and we might wish to do the same, but in a function returning a Result

結果、戻り値
:

#![allow(unused)] fn main() { fn foo() -> Result<T, E> { let x = bar()?; // propagate 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
設計(する)

Playground

Note: if you wish to experiment, this Rust playgroud link contains

含む
the traits and impls defined
定義する
herein.

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:

#![allow(unused)] fn main() { match Try::into_result(expr) { Ok(v) => v, // here, the `return` presumes that there is // no `catch` in scope: Err(e) => return Try::from_error(From::from(e)), } }

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:
下記の、次に続く、追従する

#![allow(unused)] fn main() { 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. /// /// 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<Self::Ok, Self::Error>; /// 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; } }

Initial
初期
impls

libcore will also define

定義する
the following
下記の、次に続く、追従する
impls for the following
下記の、次に続く、追従する
types.

Result

結果、戻り値

The Result

結果、戻り値
type includes an impl as follows:
下記の、次に続く、追従する

#![allow(unused)] fn main() { impl<T,E> Try for Result<T, E> { type Ok = T; type Error = E; fn into_result(self) -> Self { self } fn from_ok(v: T) -> Self { Ok(v) } fn from_error(v: E) -> Self { Err(v) } } }

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:

下記の、次に続く、追従する

#![allow(unused)] fn main() { mod option { pub struct Missing; impl<T> Try for Option<T> { type Ok = T; type Error = Missing; fn into_result(self) -> Result<T, Missing> { self.ok_or(Missing) } fn from_ok(v: T) -> Self { Some(v) } fn from_error(_: Missing) -> Self { None } } } }

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<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 converting
変換する
the value into an Option. (This rationale was originally explained in a comment by Aaron Turon.)

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 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.

This implies that callers

呼び出し側
of collect() will have to either use try!, or write an explicit
明示的な
type annotation, something like this:

#![allow(unused)] fn main() { vec.iter().map(|e| ...).collect::<Result<_, _>>()? }

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. 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

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. 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 include coverage of the ? operator
演算子
being used on a variety of types.

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 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

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 Result<T, Box<Error>> in a function that returns ()

At this point, we could likely make a suggestion such as "consider

考える、みなす
changing the return type to Result<(), Box<Error>>".

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
シグネチャ
(modulo RFC 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<T>, but ? is applied
適用する
to a Result<T, ()>), then we can instead report something like this:

? cannot be applied

適用する
to a Result<T, Box<Error>> in a function that returns Option<T>

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:

下記の、次に続く、追従する

#![allow(unused)] fn main() { 22 | fn foo(...) -> Option<T> { | --------- 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:

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

実装
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

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
結果、戻り値
.

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 (rather than, say, a generic type like ()).

Alternatives

The "essentialist" approach

When this RFC was first proposed, the Try trait looked quite different:

#![allow(unused)] fn main() { trait Try<E> { type Success; fn try(self) -> Result<Self::Success, E>; } }

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 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 this impl<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 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. 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: we wish the Try trait to be implemented
実装する
in a great number of scenarios. Some of them, like converting
変換する
Result<T,E> to Result<U,F>, 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<T> to Option<U>, 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 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 Asterisk), it was more appropriate to name the trait Try, which seems to be the best name for the operation
演算、操作
in question.

Unresolved questions

None.