Summary

This RFC improves interoperation between APIs with different error types. It proposes to:

  • Increase the flexibility of the try! macro for clients of multiple

    複数の
    libraries with disparate error types.

  • Standardize on basic functionality that any error type should have by introducing an Error trait.

  • Support easy error chaining when crossing abstraction boundaries.

The proposed changes are all library changes; no language

言語
changes are needed -- except that this proposal depends on multidispatch happening.

Motivation

Typically,

一般的に、典型的に
a module (or crate) will define
定義する
a custom error type encompassing the possible error outcomes for the operations
演算、操作
it provides,
与える
along with a custom Result instance
実例
baking in this type. For example, we have io::IoError and io::IoResult<T> = Result<T, io::IoError>, and similarly
同様に
for other libraries. Together with the try! macro, the story for interacting with errors for a single
単一の
library is reasonably good.

However, we lack infrastructure when consuming or building on errors from multiple

複数の
APIs, or abstracting over errors.

Consuming multiple
複数の
error types

Our current infrastructure for error handling does not cope well with mixed notions of error.

Abstractly, as described

記述する
by this issue, we cannot do the following:
下記の、次に続く、追従する

fn func() -> Result<T, Error> { try!(may_return_error_type_A()); try!(may_return_error_type_B()); }

Concretely, imagine a CLI application that interacts both with files and HTTP servers, using std::io and an imaginary

虚数の
http crate:

fn download() -> Result<(), CLIError> { let contents = try!(http::get(some_url)); let file = try!(File::create(some_path)); try!(file.write_str(contents)); Ok(()) }

The download function can encounter both io and http errors, and wants to report them both under the common notion of CLIError. But the try! macro only works for a single

単一の
error type at a time.

There are roughly two scenarios where multiple

複数の
library error types need to be coalesced into a common type, each with different needs: application error reporting, and library error reporting

Application error reporting: presenting errors to a user

An application is generally the "last stop" for error handling: it's the point at which remaining errors are presented

ある
to the user in some form,
形式、形態、形作る
when they cannot be handled programmatically.

As such, the data needed for application-level errors is usually related to human interaction. For a CLI application, a short text description and longer verbose description are usually all that's needed. For GUI applications, richer data is sometimes required, but usually not a full enum describing

記述する
the full range
範囲
of errors.

Concretely, then, for something like the download function above, for a CLI application, one might want CLIError to roughly be:

#![allow(unused)] fn main() { struct CLIError<'a> { description: &'a str, detail: Option<String>, ... // possibly more fields here; see detailed design } }

Ideally, one could use the try! macro as in the download example to coalesce a variety of error types into this single,

単一の
simple struct.

Library error reporting: abstraction boundaries

When one library builds on others, it needs to translate from their error types to its own. For example, a web server framework may build on a library for accessing a SQL database, and needs some way to "lift" SQL errors to its own notion of error.

In general,

一般
a library may not want to reveal the upstream libraries it relies on -- these are implementation
実装
details which may change over time. Thus,
それゆえに、従って、
it is critical that the error type of upstream libraries not leak, and "lifting" an error from one library to another is a way of imposing an abstraction boundaries.

In some cases, the right way to lift a given

与えられた
error will depend on the operation
演算、操作
and context.
文脈、背景
In other cases, though, there will be a general
一般
way to embed
埋め込む
one kind of error in another (usually via a "cause
起こす
chain"
). Both scenarios should be supported by Rust's error handling infrastructure.

Abstracting over errors

Finally, libraries sometimes need to work with errors in a generic way. For example, the serialize::Encoder type takes

とる
is generic over an arbitrary
任意の
error type E. At the moment, such types are completely arbitrary:
任意の
there is no Error trait giving common functionality expected of all errors. Consequently,
結果として
error-generic code cannot meaningfully interact with errors.

(See this issue for a concrete

具体的な/具象的な
case where a bound
制限する、結び付けられて
would be useful; note, however, that the design
設計(する)
below does not cover this use-case, as explained in Alternatives.)

Languages that provide exceptions

例外
often have standard exception
例外
classes
分類
or interfaces that guarantee
保証する
some basic functionality, including short and detailed descriptions and "causes".
起こす
We should begin developing similar
似ている、同様の
functionality in libstd to ensure
保証する
that we have an agreed-upon baseline error API.

Detailed design
設計(する)

We can address all of the problems laid out in the Motivation section

by adding
たす
some simple library code to libstd, so this RFC will actually give a full implementation.
実装

Note, however, that this implementation

実装
relies on the multidispatch proposal currently under consideration.
考慮

The proposal consists

構成される
of two pieces: a standardized Error trait and extensions to the try! macro.

The Error trait

The standard Error trait follows

下記の、次に続く、追従する
very the widespread pattern found in Exception
例外
base
基となる、基底(の)
classes
分類
in many languages:

#![allow(unused)] fn main() { pub trait Error: Send + Any { fn description(&self) -> &str; fn detail(&self) -> Option<&str> { None } fn cause(&self) -> Option<&Error> { None } } }

Every concrete

具体的な/具象的な
error type should provide at least a description. By making this a slice-returning method, it is possible to define
定義する
lightweight enum error types and then implement
実装する
this method as returning static
静的な
string slices depending on the variant.

The cause

起こす
method allows
許可する、可能にする
for cause-chaining when an error crosses abstraction boundaries. The cause
起こす
is recorded as a trait object implementing Error, which makes it possible to read off a kind of abstract backtrace (often more immediately
直後に、直接的に
helpful than a full backtrace).

The Any bound

制限する、結び付けられて
is needed to allow
許可する、可能にする
downcasting of errors. This RFC stipulates that it must be possible to downcast errors in the style of the Any trait, but leaves unspecified the exact implementation
実装
strategy. (If trait object upcasting was available, one could simply upcast to Any; otherwise,
さもなければ
we will likely need to duplicate the downcast APIs as blanket impls on Error objects.)

It's worth comparing

比較する
the Error trait to the most widespread error type in libstd, IoError:

#![allow(unused)] fn main() { pub struct IoError { pub kind: IoErrorKind, pub desc: &'static str, pub detail: Option<String>, } }

Code that returns or asks for an IoError explicitly

明示的に
will be able to access the kind field and thus
それゆえに、従って、
react differently to different kinds of errors. But code that works with a generic Error (e.g., application code) sees only the human-consumable parts of the error. In particular, application code will often employ Box<Error> as the error type when reporting errors to the user. The try! macro support, explained below, makes doing so ergonomic.

An extended
拡張された
try! macro

The other piece to the proposal is a way for try! to automatically

自動的に
convert
変換する
between different types of errors.

The idea is to introduce

導入する
a trait FromError<E> that says how to convert
変換する
from some lower-level error type E to Self. The try! macro then passes the error it is given
与えられた
through this conversion
変換
before returning:

#![allow(unused)] fn main() { // E here is an "input" for dispatch, so conversions from multiple error // types can be provided pub trait FromError<E> { fn from_err(err: E) -> Self; } impl<E> FromError<E> for E { fn from_err(err: E) -> E { err } } impl<E: Error> FromError<E> for Box<Error> { fn from_err(err: E) -> Box<Error> { box err as Box<Error> } } macro_rules! try ( ($expr:expr) => ({ use error; match $expr { Ok(val) => val, Err(err) => return Err(error::FromError::from_err(err)) } }) ) }

This code depends on multidispatch, because the conversion

変換
depends on both the source
元の
and target error types. (In today's Rust, the two implementations
実装
of FromError given
与えられた
above would be considered
みなす、考慮する
overlapping.)

Given

与えられた
the blanket impl of FromError<E> for E, all existing uses of try! would continue to work as-is.

With this infrastructure in place, application code can generally use Box<Error> as its error type, and try! will take

とる
care of the rest:

fn download() -> Result<(), Box<Error>> { let contents = try!(http::get(some_url)); let file = try!(File::create(some_path)); try!(file.write_str(contents)); Ok(()) }

Library code that defines

定義する
its own error type can define
定義する
custom FromError implementations
実装
for lifting lower-level errors (where the lifting should also perform cause
起こす
chaining) -- at least when the lifting is uniform across the library. The effect is that the mapping from one error type into another only has to be written one, rather than at every use of try!:

impl FromError<ErrorA> MyError { ... } impl FromError<ErrorB> MyError { ... } fn my_lib_func() -> Result<T, MyError> { try!(may_return_error_type_A()); try!(may_return_error_type_B()); }

Drawbacks

The main drawback is that the try! macro is a bit more complicated.

Unresolved questions

Conventions

This RFC does not define

定義する
any particular conventions around cause
起こす
chaining or concrete
具体的な/具象的な
error types. It will likely take
とる
some time and experience using the proposed infrastructure before we can settle these conventions.

Extensions

The functionality in the Error trait is quite minimal, and should probably grow over time. Some additional

追加の
functionality might include:

Features on the Error trait

  • Generic creation of Errors. It might be useful for the Error trait to expose an associated

    関連付けられた
    constructor.
    function Object() { [native code] }
    See this issue for an example where this functionality would be useful.

  • Mutation of Errors. The Error trait could be expanded to provide setters as well as getters.

The main reason not to include the above two features is so that Error can be used with extremely minimal data structures, e.g. simple enums. For such data structures, it's possible to produce

産出する
fixed descriptions, but not mutate descriptions or other error properties.
特徴、性質
Allowing
許可する、可能にする
generic creation of any Error-bounded type would also require these enums to include something like a GenericError variant, which is unfortunate. So for now, the design
設計(する)
sticks to the least common denominator.

Concrete
具体的な/具象的な
error types

On the other hand, for code that doesn't care about the footprint of its error types, it may be useful to provide something like the following

下記の、次に続く、追従する
generic error type:

#![allow(unused)] fn main() { pub struct WrappedError<E> { pub kind: E, pub description: String, pub detail: Option<String>, pub cause: Option<Box<Error>> } impl<E: Show> WrappedError<E> { pub fn new(err: E) { WrappedErr { kind: err, description: err.to_string(), detail: None, cause: None } } } impl<E> Error for WrappedError<E> { fn description(&self) -> &str { self.description.as_slice() } fn detail(&self) -> Option<&str> { self.detail.as_ref().map(|s| s.as_slice()) } fn cause(&self) -> Option<&Error> { self.cause.as_ref().map(|c| &**c) } } }

This type can easily be added

たす
later, so again this RFC sticks to the minimal functionality for now.