diff --git a/1.6/ja/book/error-handling.md b/1.6/ja/book/error-handling.md index b2c84116..3baa6fcf 100644 --- a/1.6/ja/book/error-handling.md +++ b/1.6/ja/book/error-handling.md @@ -72,12 +72,12 @@ Rustでは戻り値を使います。 * [整数をパースする](#整数をパースする) * [`Result` 型エイリアスを用いたイディオム](#result-型エイリアスを用いたイディオム) * [小休止:アンラップは悪ではない](#小休止アンラップは悪ではない) -* [複数のエラー型を扱う](#working-with-multiple-error-types) - * [`Option` と `Result` を合成する](#composing-option-and-result) - * [コンビネータの限界](#the-limits-of-combinators) - * [早期のリターン](#early-returns) - * [`try!` マクロ](#the-try-macro) - * [独自のエラー型を定義する](#defining-your-own-error-type) +* [複数のエラー型を扱う](#複数のエラー型を扱う) + * [`Option` と `Result` を合成する](#option-と-result-を合成する) + * [コンビネータの限界](#コンビネータの限界) + * [早期リターン](#早期リターン) + * [`try!` マクロ](#try-マクロ) + * [独自のエラー型を定義する](#独自のエラー型を定義する) * [標準ライブラリのトレイトによるエラー処理](#standard-library-traits-used-for-error-handling) * [`Error` トレイト](#the-error-trait) * [`From` トレイト](#the-from-trait) @@ -101,8 +101,8 @@ Rustでは戻り値を使います。 -エラーハンドリングとは、ある処理が成功したかどうかを *ケース分析* に基づいて判断するものだと考えられます。 -これから見ていくように、エラーハンドリングをエルゴノミックにするために重要なのは、プログラマがコードを合成可能(composable) に保ったまま、明示的なケース分析の回数を、いかに減らしていくかということです。 +エラーハンドリングとは、ある処理が成功したかどうかを *場合分け(case analysis)* に基づいて判断するものだと考えられます。 +これから見ていくように、エラーハンドリングをエルゴノミックにするために重要なのは、プログラマがコードを合成可能(composable) に保ったまま、明示的な場合分けの回数を、いかに減らしていくかということです。 @@ -182,7 +182,7 @@ fn main() { -先ほどの例で、プログラムが2つのエラー条件のいずれかを満たした時に、パニックすると言いました。 +先ほどの例で、プログラムが2つのエラー条件のいずれかを満たしたときに、パニックすると言いました。 でもこのプログラムは、最初の例とは違って明示的に `panic` を呼び出してはいません。 実はパニックは `unwrap` の呼び出しの中に埋め込まれているのです。 @@ -191,7 +191,7 @@ fn main() { -Rustでなにかを「アンラップする」時、こう言っているのと同じです。 +Rustでなにかを「アンラップする」とき、こう言っているのと同じです。 「計算結果を取り出しなさい。もしエラーになっていたのなら、パニックを起こしてプログラムを終了させなさい。」 アンラップのコードはとてもシンプルなので、多分、それを見せたほうが早いでしょう。 でもそのためには、まず `Option` と `Result` 型について調べる必要があります。 @@ -244,7 +244,7 @@ fn find(haystack: &str, needle: char) -> Option { -この関数がマッチする文字を見つけた時、単に `offset` を返すだけではないことに注目してください。 +この関数がマッチする文字を見つけたとき、単に `offset` を返すだけではないことに注目してください。 その代わりに `Some(offset)` を返します。 `Some` は `Option` 型の *値コンストラクタ* の一つです。 これは `fn(value: T) -> Option` という型の関数だと考えることもできます。 @@ -280,16 +280,16 @@ fn main() { -このコードは `find` 関数が返した `Option` の *ケース分析* に、 [パターンマッチ][1] を使っています。 -実のところ、ケース分析が、`Option` に格納された値を取り出すための唯一の方法なのです。 -これは、`Option` が `Some(t)` ではなく `None` だった時、プログラマであるあなたが、このケースに対処しなければならないことを意味します。 +このコードは `find` 関数が返した `Option` の *場合分け* に、 [パターンマッチ][1] を使っています。 +実のところ、場合分けが、`Option` に格納された値を取り出すための唯一の方法なのです。 +これは、`Option` が `Some(t)` ではなく `None` だったとき、プログラマであるあなたが、このケースに対処しなければならないことを意味します。 でも、ちょっと待ってください。 [さっき](#code-unwrap-double) 使った `unwrap` はどうだったでしょうか? -ケース分析はどこにもありませんでした! -実はケース分析は `unwrap` メソッドの中に埋め込まれていたのです。 +場合分けはどこにもありませんでした! +実は場合分けは `unwrap` メソッドの中に埋め込まれていたのです。 もし望むなら、このように自分で定義することもできます: @@ -319,7 +319,7 @@ impl Option { -`unwrap` メソッドは *ケース分析を抽象化します* 。このことは確かに `unwrap` をエルゴノミックにしています。 +`unwrap` メソッドは *場合分けを抽象化します* 。このことは確かに `unwrap` をエルゴノミックにしています。 しかし残念なことに、そこにある `panic!` が意味するものは、`unwrap` が合成可能ではない、つまり、陶器店の中の雄牛だということです。 @@ -373,12 +373,12 @@ fn extension_explicit(file_name: &str) -> Option<&str> { このコードはいたってシンプルですが、ひとつだけ注目して欲しいのは、`find` の型が不在の可能性について考慮することを強制していることです。 これは良いことです。なぜなら、コンパイラが私たちに、ファイル名が拡張子を持たないケースを、うっかり忘れないようにしてくれるからです。 -しかし一方で、 `extension_explicit` でしたような明示的なケース分析を毎回続けるのは、なかなか面倒です。 +しかし一方で、 `extension_explicit` でしたような明示的な場合分けを毎回続けるのは、なかなか面倒です。 -実は `extension_explicit` でのケース分析は、ごく一般的なパターンである、`Option` への *map* の適用に当てはめられます。 +実は `extension_explicit` での場合分けは、ごく一般的なパターンである、`Option` への *map* の適用に当てはめられます。 これは、もしオプションが `None` なら `None` を返し、そうでなけれは、オプションの中の値に関数を適用する、というパターンです。 @@ -401,7 +401,7 @@ fn map(option: Option, f: F) -> Option where F: FnOnce(T) -> A { -新しいコンビネータを手に入れましたので、 `extension_explicit` メソッドを書き直して、ケース分析を省きましょう: +新しいコンビネータを手に入れましたので、 `extension_explicit` メソッドを書き直して、場合分けを省きましょう: ```rust # fn find(_: &str, _: char) -> Option { None } @@ -421,9 +421,9 @@ fn extension(file_name: &str) -> Option<&str> { -もう一つの共通のパターンは、`Option` の値が `None` だった時のデフォルト値を与えることです。 -例えばファイルの拡張子がない時は、それを `rs` とみなすようなプログラムを書きたくなるかもしれません。 -ご想像の通り、このようなケース分析はファイルの拡張子に特有のものではありません。 +もう一つの共通のパターンは、`Option` の値が `None` だったときのデフォルト値を与えることです。 +例えばファイルの拡張子がないときは、それを `rs` とみなすようなプログラムを書きたくなるかもしれません。 +ご想像の通り、このような場合分けはファイルの拡張子に特有のものではありません。 どんな `Option` でも使えるでしょう: ```rust @@ -481,7 +481,7 @@ fn main() { つまり、与えられたファイル *パス* から拡張子を見つけ出せるか、トライしなければなりません。 -まず明示的なケース分析から始めましょう: +まず明示的な場合分けから始めましょう: ```rust @@ -509,7 +509,7 @@ fn file_name(file_path: &str) -> Option<&str> { -ケース分析を減らすために単に `map` コンビネータを使えばいいと思うかもしれませんが、型にうまく適合しません。 +場合分けを減らすために単に `map` コンビネータを使えばいいと思うかもしれませんが、型にうまく適合しません。 なぜなら `map` が引数にとる関数は、中の値だけに適用されるからです。 そして関数が返した値は *必ず* [`Some` でラップされ直します](#code-option-map) 。 つまりこの代わりに、 `map` に似ていながら、呼び出し元が別の `Option` を返せるしくみが必要です。 @@ -526,7 +526,7 @@ fn and_then(option: Option, f: F) -> Option ``` -では、明示的なケース分析を省くように、 `file_path_ext` を書き直しましょう: +では、明示的な場合分けを省くように、 `file_path_ext` を書き直しましょう: ```rust # fn extension(file_name: &str) -> Option<&str> { None } @@ -544,7 +544,7 @@ fn file_path_ext(file_path: &str) -> Option<&str> { `Option` 型には、他にもたくさんのコンビネータが [標準ライブラリで定義されています][5] 。 それらの一覧をざっと眺めて、なにがあるか知っておくといいでしょう。 -大抵の場合、ケース分析を減らすのに役立ちます。 +大抵の場合、場合分けを減らすのに役立ちます。 それらのコンビネータに慣れるための努力は、すぐに報われるでしょう。 なぜなら、そのほとんどは次に話す `Result` 型でも、(よく似たセマンティクスで)定義されているからです。 @@ -552,9 +552,9 @@ fn file_path_ext(file_path: &str) -> Option<&str> { -コンビネータは明示的なケース分析を減らしてくれるので、 `Option` のような型をエルゴノミックにします。 +コンビネータは明示的な場合分けを減らしてくれるので、 `Option` のような型をエルゴノミックにします。 またこれらは *不在の可能性* を、呼び出し元がそれに合った方法で扱えるようにするので、合成可能だといえます。 -`unwrap` のようなメソッドは、 `Option` が `None` の時にパニックを起こすので、このような選択の機会を与えません。 +`unwrap` のようなメソッドは、 `Option` が `None` のときにパニックを起こすので、このような選択の機会を与えません。 ## `Result` 型 @@ -707,7 +707,7 @@ impl str { それも悪いやり方ではありませんが、実装の内側では *なぜ* 文字列が整数としてパースできなかったを、ちゃんと区別しています。 (空の文字列だったのか、有効な数字でなかったのか、大きすぎたり、小さすぎたりしたのか。) 従って、`Result` を使ってより多くの情報を提供するほうが、単に「不在」を示すことよりも理にかなっています。 -今後、もし `Option` と `Result` のどちらを選ぶという事態に遭遇した時は、このような理由付けのやり方を真似てみてください。 +今後、もし `Option` と `Result` のどちらを選ぶという事態に遭遇したときは、このような理由付けのやり方を真似てみてください。 もし詳細なエラー情報を提供できるのなら、多分、それをしたほうがいいでしょう。 (後ほど別の例もお見せます。) @@ -752,7 +752,7 @@ fn main() { これで少し良くなりましたが、たくさんのコードを書いてしまいました! -ケース分析に、またしてもやられたわけです。 +場合分けに、またしてもやられたわけです。 @@ -855,7 +855,7 @@ fn double_number(number_str: &str) -> Result { * **即興で書いたサンプルコード。** サンプルコードや簡単なプログラムを書いていて、エラーハンドリングが単に重要でないこともあります。 - このような時に `unwrap` の便利さは、とても魅力的に映るでしょう。 + このようなときに `unwrap` の便利さは、とても魅力的に映るでしょう。 これに打ち勝つのは難しいことです。 @@ -863,8 +863,8 @@ fn double_number(number_str: &str) -> Result { -* **パニックがプログラムのバグの兆候となる時。** - コードの中の不変条件が、ある特定のケースの発生を未然に防ぐ時(例えば、空のスタックから取り出そうとしたなど)、パニックを起こしても差し支えありません。 +* **パニックがプログラムのバグの兆候となるとき。** + コードの中の不変条件が、ある特定のケースの発生を未然に防ぐとき(例えば、空のスタックから取り出そうとしたなど)、パニックを起こしても差し支えありません。 なぜなら、そうすることでプログラムに潜むバグが明るみに出るからです。 これは `assert!` の失敗のような明示的な要因によるものだったり、配列のインデックスが境界から外れたからだったりします。 @@ -876,7 +876,7 @@ fn double_number(number_str: &str) -> Result { これは多分、完全なリストではないでしょう。 -さらに `Option` を使う時は、ほとんどの場合で [`expect`](../std/option/enum.Option.html#method.expect) メソッドを使う方がいいでしょう。 +さらに `Option` を使うときは、ほとんどの場合で [`expect`](../std/option/enum.Option.html#method.expect) メソッドを使う方がいいでしょう。 `expect` は `unwrap` とほぼ同じことをしますが、 `expect` では与えられたメッセージを表示するところが異なります。 この方が結果として起こったパニックを、少し扱いやすいものにします。 なぜなら「 `None` な値に対してアンラップが呼ばれました」というメッセージの代わりに、指定したメッセージが表示されるからです。 @@ -898,48 +898,70 @@ fn double_number(number_str: &str) -> Result { また、アンラップについても解説しました。 では標準ライブラリをもっと探索していきましょう。 -# Working with multiple error types - -Thus far, we've looked at error handling where everything was either an -`Option` or a `Result`. But what happens when you have both an -`Option` and a `Result`? Or what if you have a `Result` and a -`Result`? Handling *composition of distinct error types* is the next -challenge in front of us, and it will be the major theme throughout the rest of -this chapter. - -## Composing `Option` and `Result` - -So far, I've talked about combinators defined for `Option` and combinators -defined for `Result`. We can use these combinators to compose results of -different computations without doing explicit case analysis. - -Of course, in real code, things aren't always as clean. Sometimes you have a -mix of `Option` and `Result` types. Must we resort to explicit case analysis, -or can we continue using combinators? - -For now, let's revisit one of the first examples in this chapter: + +# 複数のエラー型を扱う + + +これまで見てきたエラーハンドリングでは、 `Option` または `Result` が1つあるだけでした。 +ではもし `Option` と `Result` の両方があったらどうなるでしょうか? +あるいは、`Result` と `Result` があったら? +*異なるエラー型の組み合わせ* を扱うことが、いま目の前にある次なる課題です。 +またこれが、この章の残りの大半に共通する、主要なテーマとなります。 + + +## `Option` と `Result` を合成する + + + + +これまで話してきたのは `Option` のために定義されたコンビネータと、 `Result` のために定義されたコンビネータについてでした。 +これらのコンビネータを使うと、様々な処理の結果を明示的な場合分けなしに組み合わせることができました。 + + + + +もちろん現実のコードは、いつもこんなにクリーンではありません。 +時には `Option` 型と `Result` 型が混在していることもあるでしょう。 +そんなときは、明示的な場合分けに頼るしかないのでしょうか? +それとも、コンビネータを使い続けることができるのでしょうか? + + +ここで、この章の最初の方にあった例に戻ってみましょう: ```rust,should_panic use std::env; fn main() { let mut argv = env::args(); - let arg: String = argv.nth(1).unwrap(); // error 1 - let n: i32 = arg.parse().unwrap(); // error 2 + let arg: String = argv.nth(1).unwrap(); // エラー1 + let n: i32 = arg.parse().unwrap(); // エラー2 println!("{}", 2 * n); } ``` -Given our new found knowledge of `Option`, `Result` and their various -combinators, we should try to rewrite this so that errors are handled properly -and the program doesn't panic if there's an error. + + + +これまでに獲得した知識、つまり `Option` 、`Result` と、それらのコンビネータに関する知識を動員して、これを書き換えましょう。 +エラーを適切に処理し、もしエラーが起こっても、プログラムがパニックしないようにするのです。 -The tricky aspect here is that `argv.nth(1)` produces an `Option` while -`arg.parse()` produces a `Result`. These aren't directly composable. When faced -with both an `Option` and a `Result`, the solution is *usually* to convert the -`Option` to a `Result`. In our case, the absence of a command line parameter -(from `env::args()`) means the user didn't invoke the program correctly. We -could just use a `String` to describe the error. Let's try: + + + + + + +ここでの問題は `argv.nth(1)` が `Option` を返すのに、 `arg.parse()` は `Result` を返すことです。 +これらを直接合成することはできません。 +`Option` と `Result` の両方に出会ったときの *通常の* 解決策は `Option` を `Result` に変換することです。 +この例で(`env::args()` が)コマンドライン引数を返さなかったということは、ユーザーがプログラムを正しく起動しなかったことを意味します。 +エラーの理由を示すために、単純に `String` を使うこともできます。 +試してみましょう: @@ -961,11 +983,20 @@ fn main() { } ``` -There are a couple new things in this example. The first is the use of the -[`Option::ok_or`](../std/option/enum.Option.html#method.ok_or) -combinator. This is one way to convert an `Option` into a `Result`. The -conversion requires you to specify what error to use if `Option` is `None`. -Like the other combinators we've seen, its definition is very simple: +> 訳注: +> +> Please give at least one argument:引数を最低1つ指定してください。 + + + + + + +この例では、いくつか新しいことがあります。 +ひとつ目は [`Option::ok_or`](../std/option/enum.Option.html#method.ok_or) コンビネータを使ったことです。 +これは `Option` を `Result` へ変換する方法の一つです。 +変換には `Option` が `None` のときに使われるエラーを指定する必要があります。 +他のコンビネータと同様に、その定義はとてもシンプルです: ```rust fn ok_or(option: Option, err: E) -> Result { @@ -976,32 +1007,49 @@ fn ok_or(option: Option, err: E) -> Result { } ``` -The other new combinator used here is -[`Result::map_err`](../std/result/enum.Result.html#method.map_err). -This is just like `Result::map`, except it maps a function on to the *error* -portion of a `Result` value. If the `Result` is an `Ok(...)` value, then it is -returned unmodified. - -We use `map_err` here because it is necessary for the error types to remain -the same (because of our use of `and_then`). Since we chose to convert the -`Option` (from `argv.nth(1)`) to a `Result`, we must -also convert the `ParseIntError` from `arg.parse()` to a `String`. - -## The limits of combinators - -Doing IO and parsing input is a very common task, and it's one that I -personally have done a lot of in Rust. Therefore, we will use (and continue to -use) IO and various parsing routines to exemplify error handling. - -Let's start simple. We are tasked with opening a file, reading all of its -contents and converting its contents to a number. Then we multiply it by `2` -and print the output. - -Although I've tried to convince you not to use `unwrap`, it can be useful -to first write your code using `unwrap`. It allows you to focus on your problem -instead of the error handling, and it exposes the points where proper error -handling need to occur. Let's start there so we can get a handle on the code, -and then refactor it to use better error handling. + + + + + +ここで使った、もう一つの新しいコンビネータは [`Result::map_err`](../std/result/enum.Result.html#method.map_err) です。 +これは `Result::map` に似ていますが、 `Result` 値の *エラー* の部分に対して関数をマップするところが異なります。 +もし `Result` の値が `Ok(...)` だったら、そのまま変更せずに返します。 + + + + + +`map_err` を使った理由は、(`and_then` の用法により)エラーの型を同じに保つ必要があったからです。 +ここでは(`argv.nth(1)`が返した) `Option` を `Result` に変換することを選んだため、`arg.parse()` が返した `ParseIntError` も `String` に変換しなければならなかったわけです。 + + +## コンビネータの限界 + + + + +入出力と共に入力をパースすることは、非常によく行われます。 +そして私がRustを使って個人的にやってきたことのほとんども、これに該当しています。 +ですから、ここでは(そして、この後も) IOと様々なパースを行うルーチンを、エラーハンドリングの例として扱っていきます。 + + + + +まずは簡単なものから始めましょう。 +ここでのタスクは、ファイルを開き、その内容を全て読み込み、1つの数値に変換することです。 +そしてそれに `2` を掛けて、結果を表示します。 + + + + + + +いままで `unwrap` を使わないよう説得してきたわけですが、最初にコードを書くときには `unwrap` が便利に使えます。 +こうすることで、エラーハンドリングではなく、本来解決すべき課題に集中できます。 +それと同時に `unwrap` は、適切なエラーハンドリングが必要とされる場所を教えてくれます。 +ここから始めることをコーディングへの取っ掛かりとしましょう。 +その後、リファクタリングによって、エラーハンドリングを改善していきます。 ```rust,should_panic use std::fs::File; @@ -1009,10 +1057,10 @@ use std::io::Read; use std::path::Path; fn file_double>(file_path: P) -> i32 { - let mut file = File::open(file_path).unwrap(); // error 1 + let mut file = File::open(file_path).unwrap(); // エラー1 let mut contents = String::new(); - file.read_to_string(&mut contents).unwrap(); // error 2 - let n: i32 = contents.trim().parse().unwrap(); // error 3 + file.read_to_string(&mut contents).unwrap(); // エラー2 + let n: i32 = contents.trim().parse().unwrap(); // エラー3 2 * n } @@ -1022,47 +1070,76 @@ fn main() { } ``` -(N.B. The `AsRef` is used because those are the -[same bounds used on -`std::fs::File::open`](../std/fs/struct.File.html#method.open). -This makes it ergonomic to use any kind of string as a file path.) - -There are three different errors that can occur here: - -1. A problem opening the file. -2. A problem reading data from the file. -3. A problem parsing the data as a number. - -The first two problems are described via the -[`std::io::Error`](../std/io/struct.Error.html) type. We know this -because of the return types of -[`std::fs::File::open`](../std/fs/struct.File.html#method.open) and -[`std::io::Read::read_to_string`](../std/io/trait.Read.html#method.read_to_string). -(Note that they both use the [`Result` type alias -idiom](#the-result-type-alias-idiom) described previously. If you -click on the `Result` type, you'll [see the type -alias](../std/io/type.Result.html), and consequently, the underlying -`io::Error` type.) The third problem is described by the -[`std::num::ParseIntError`](../std/num/struct.ParseIntError.html) -type. The `io::Error` type in particular is *pervasive* throughout the -standard library. You will see it again and again. - -Let's start the process of refactoring the `file_double` function. To make this -function composable with other components of the program, it should *not* panic -if any of the above error conditions are met. Effectively, this means that the -function should *return an error* if any of its operations fail. Our problem is -that the return type of `file_double` is `i32`, which does not give us any -useful way of reporting an error. Thus, we must start by changing the return -type from `i32` to something else. - -The first thing we need to decide: should we use `Option` or `Result`? We -certainly could use `Option` very easily. If any of the three errors occur, we -could simply return `None`. This will work *and it is better than panicking*, -but we can do a lot better. Instead, we should pass some detail about the error -that occurred. Since we want to express the *possibility of error*, we should -use `Result`. But what should `E` be? Since two *different* types of -errors can occur, we need to convert them to a common type. One such type is -`String`. Let's see how that impacts our code: + + + + +(備考: `AsRef` を使ったのは、[`std::fs::File::open` で使われているものと同じ境界](../std/fs/struct.File.html#method.open) だからです。 +ファイルパスとして、どんな文字列でも受け付けるので、エルゴノミックになります。) + + +ここでは3種類のエラーが起こる可能性があります: + + + + +1. ファイルを開くときの問題 +2. ファイルからデータを読み込むときの問題 +3. データを数値としてパースするときの問題 + + + + + + + + + + + + + + +最初の2つの問題は、[`std::io::Error`](../std/io/struct.Error.html) 型で記述されます。 +これは [`std::fs::File::open`](../std/fs/struct.File.html#method.open) と [`std::io::Read::read_to_string`](../std/io/trait.Read.html#method.read_to_string) のリターン型からわかります。 +(ちなみにどちらも、以前紹介した [`Result` 型エイリアスのイディオム](#result-型エイリアスを用いたイディオム) を用いています。 +`Result` 型のところをクリックすると、いま言った [型エイリアスを見たり](../std/io/type.Result.html)、必然的に、中で使われている `io::Error` 型も見ることになるでしょう。) +3番目の問題は [`std::num::ParseIntError`](../std/num/struct.ParseIntError.html) 型で記述されます。 +特にこの `io::Error` 型は標準ライブラリ全体に *深く浸透しています* 。 +これからこの型を幾度となく見ることでしょう。 + + + + + + + + +まず最初に `file_double` 関数をリファクタリングしましょう。 +この関数を、このプログラムの他の構成要素と合成可能にするためには、上記の問題のいずれかに遭遇しても、パニック *しない* ようにしなければなりません。 +これは実質的には、なにかの操作に失敗したときに、この関数が *エラーを返すべき* であることを意味します。 +ここでの問題は、`file_double` のリターン型が `i32` であるため、エラーの報告には全く役立たないことです。 +従ってリターン型を `i32` から別の何かに変えることから始めましょう。 + + + + + + + + + +最初に決めるべきことは、 `Option` と `Result` のどちらを使うかです。 +`Option` なら間違いなく簡単に使えます。 +もし3つのエラーのどれかが起こったら、単に `None` を返せばいいのですから。 +これはたしかに動きますし、 *パニックを起こすよりは良くなっています* 。 +とはいえ、もっと良くすることもできます。 +`Option` の代わりに、発生したエラーについての詳細を渡すべきでしょう。 +ここでは *エラーの可能性* を示したいのですから、`Result` を使うのがよさそうです。 +でも `E` を何にしたらいいのでしょうか? +2つの *異なる* 型のエラーが起こり得ますので、これらを共通の型に変換する必要があります。 +そのような型の一つに `String` があります。 +この変更がコードにどんな影響を与えるか見てみましょう: ```rust use std::fs::File; @@ -1093,39 +1170,62 @@ fn main() { } ``` -This code looks a bit hairy. It can take quite a bit of practice before code -like this becomes easy to write. The way we write it is by *following the -types*. As soon as we changed the return type of `file_double` to -`Result`, we had to start looking for the right combinators. In -this case, we only used three different combinators: `and_then`, `map` and -`map_err`. - -`and_then` is used to chain multiple computations where each computation could -return an error. After opening the file, there are two more computations that -could fail: reading from the file and parsing the contents as a number. -Correspondingly, there are two calls to `and_then`. - -`map` is used to apply a function to the `Ok(...)` value of a `Result`. For -example, the very last call to `map` multiplies the `Ok(...)` value (which is -an `i32`) by `2`. If an error had occurred before that point, this operation -would have been skipped because of how `map` is defined. - -`map_err` is the trick that makes all of this work. `map_err` is just like -`map`, except it applies a function to the `Err(...)` value of a `Result`. In -this case, we want to convert all of our errors to one type: `String`. Since -both `io::Error` and `num::ParseIntError` implement `ToString`, we can call the -`to_string()` method to convert them. - -With all of that said, the code is still hairy. Mastering use of combinators is -important, but they have their limits. Let's try a different approach: early -returns. - -## Early returns - -I'd like to take the code from the previous section and rewrite it using *early -returns*. Early returns let you exit the function early. We can't return early -in `file_double` from inside another closure, so we'll need to revert back to -explicit case analysis. + + + + + + +このコードは、やや難解になってきました。 +このようなコードを簡単に書けるようになるまでには、結構な量の練習が必要かもしれません。 +こういうもの書くときは *型に導かれる* ようにします。 +`file_double` のリターン型を `Result` に変更したらすぐに、それに合ったコンビネータを探し始めるのです。 +この例では `and_then`, `map`, `map_err` の、3種類のコンビネータだけを使いました。 + + + + + +`and_then` は、エラーを返すかもしれない処理同士を繋いでいくために使います。 +ファイルを開いた後に、失敗するかもしれない処理が2つあります: +ファイルからの読み込む所と、内容を数値としてパースする所です。 +これに対応して `and_then` も2回呼ばれています。 + + + + + +`map` は `Result` の値が `Ok(...)` のときに関数を適用するために使います。 +例えば、一番最後の `map` の呼び出しは、`Ok(...)` の値( `i32` 型)に `2` を掛けます。 +もし、これより前にエラーが起きたなら、この操作は `map` の定義に従ってスキップされます。 + + + + + + +`map_err` は全体をうまく動かすための仕掛けです。 +`map_err` は `map` に似ていますが、 `Result` の値が `Err(...)` のときに関数を適用するところが異なります。 +今回の場合は、全てのエラーを `String` という同一の型に変換する予定でした。 +`io::Error` と `num::ParseIntError` の両方が `ToString` を実装していたので、 `to_string()` メソッドを呼ぶことで変換できました。 + + + + +説明し終わった後でも、このコードは難解なままです。 +コンビネータの使い方をマスターすることは重要ですが、コンビネータには限界もあるのです。 +次は、早期リターンと呼ばれる、別のアプローチを試してみましょう。 + + +## 早期リターン + + + + + +前の節で使ったコードを、 *早期リターン* を使って書き直してみようと思います。 +早期リターンとは、関数の途中で抜けることを指します。 +`file_double` のクロージャの中にいる間は、早期リターンはできないので、明示的な場合分けまでいったん戻る必要があります。 ```rust use std::fs::File; @@ -1156,25 +1256,40 @@ fn main() { } ``` -Reasonable people can disagree over whether this code is better that the code -that uses combinators, but if you aren't familiar with the combinator approach, -this code looks simpler to read to me. It uses explicit case analysis with -`match` and `if let`. If an error occurs, it simply stops executing the -function and returns the error (by converting it to a string). - -Isn't this a step backwards though? Previously, we said that the key to -ergonomic error handling is reducing explicit case analysis, yet we've reverted -back to explicit case analysis here. It turns out, there are *multiple* ways to -reduce explicit case analysis. Combinators aren't the only way. - -## The `try!` macro - -A cornerstone of error handling in Rust is the `try!` macro. The `try!` macro -abstracts case analysis just like combinators, but unlike combinators, it also -abstracts *control flow*. Namely, it can abstract the *early return* pattern -seen above. - -Here is a simplified definition of a `try!` macro: + + + + + +このコードが、コンビネータを使ったコードよりも良くなったのかについては、人によって意見が分かれるでしょう。 +でも、もしあなたがコンビネータによるアプローチに不慣れだったら、このコードのほうが読みやすいと思うかもしれません。 +ここでは明示的な場合分けを `match` と `if let` で行っています。 +もしエラーが起きたら関数の実行を打ち切って、エラーを(文字列に変換してから)返します。 + + + + + +でもこれって逆戻りしてませんか? +以前は、エラーハンドリングをエルゴノミックにするために、明示的な場合分けを減らすべきだと言っていました。 +それなのに、今は明示的な場合分けに戻ってしまっています。 +すぐにわかりますが、明示的な場合分けを減らす方法は *複数* あるのです。 +コンビネータが唯一の方法ではありません。 + + +## `try!` マクロ + + + + + +Rustでのエラー処理の基礎となるのは `try!` マクロです。 +`try!` マクロはコンビネータと同様、場合分けを抽象化します。 +しかし、コンビネータと異なるのは *制御フロー* も抽象化してくれることです。 +つまり、先ほど見た *早期リターン* のパターンを抽象化できるのです。 + + +`try!` マクロの簡略化した定義はこうなります: @@ -1187,12 +1302,16 @@ macro_rules! try { } ``` -(The [real definition](../std/macro.try!.html) is a bit more -sophisticated. We will address that later.) + + +([本当の定義](../std/macro.try!.html) はもっと洗練されています。 +後ほど紹介します。) -Using the `try!` macro makes it very easy to simplify our last example. Since -it does the case analysis and the early return for us, we get tighter code that -is easier to read: + + + +`try!` マクロを使うと、最後の例をシンプルにすることが、とても簡単にできます。 +場合分けと早期リターンを肩代わりしてくれますので、コードが締まって読みやすくなります。 ```rust use std::fs::File; @@ -1215,61 +1334,92 @@ fn main() { } ``` -The `map_err` calls are still necessary given -[our definition of `try!`](#code-try-def-simple). -This is because the error types still need to be converted to `String`. -The good news is that we will soon learn how to remove those `map_err` calls! -The bad news is that we will need to learn a bit more about a couple important -traits in the standard library before we can remove the `map_err` calls. - -## Defining your own error type - -Before we dive into some of the standard library error traits, I'd like to wrap -up this section by removing the use of `String` as our error type in the -previous examples. - -Using `String` as we did in our previous examples is convenient because it's -easy to convert errors to strings, or even make up your own errors as strings -on the spot. However, using `String` for your errors has some downsides. - -The first downside is that the error messages tend to clutter your -code. It's possible to define the error messages elsewhere, but unless -you're unusually disciplined, it is very tempting to embed the error -message into your code. Indeed, we did exactly this in a [previous -example](#code-error-double-string). - -The second and more important downside is that `String`s are *lossy*. That is, -if all errors are converted to strings, then the errors we pass to the caller -become completely opaque. The only reasonable thing the caller can do with a -`String` error is show it to the user. Certainly, inspecting the string to -determine the type of error is not robust. (Admittedly, this downside is far -more important inside of a library as opposed to, say, an application.) - -For example, the `io::Error` type embeds an -[`io::ErrorKind`](../std/io/enum.ErrorKind.html), -which is *structured data* that represents what went wrong during an IO -operation. This is important because you might want to react differently -depending on the error. (e.g., A `BrokenPipe` error might mean quitting your -program gracefully while a `NotFound` error might mean exiting with an error -code and showing an error to the user.) With `io::ErrorKind`, the caller can -examine the type of an error with case analysis, which is strictly superior -to trying to tease out the details of an error inside of a `String`. - -Instead of using a `String` as an error type in our previous example of reading -an integer from a file, we can define our own error type that represents errors -with *structured data*. We endeavor to not drop information from underlying -errors in case the caller wants to inspect the details. - -The ideal way to represent *one of many possibilities* is to define our own -sum type using `enum`. In our case, an error is either an `io::Error` or a -`num::ParseIntError`, so a natural definition arises: + + + + + + +[今の私たちの `try!` の定義](#code-try-def-simple) ですと、 `map_err` は今でも必要です。 +なぜなら、エラー型を `String` に変換しなければならないからです。 +でも、いい知らせがあります。 +`map_err` の呼び出しを省く方法をすぐに習うのです! +悪い知らせは、`map_err` を省く前に、標準ライブラリのいくつかの重要なトレイトについて、もう少し学ぶ必要があるということです。 + + +## 独自のエラー型を定義する + + + + +標準ライブラリのいくつかのエラートレイトについて学ぶ前に、これまでの例にあったエラー型における `String` の使用を取り除くことで、この節を締めくくりたいと思います。 + + + + +これまでの例では `String` を便利に使ってきました。 +なぜなら、エラーは簡単に文字列へ変換できますし、問題が起こったその場で、文字列によるエラーを新たに作ることもできるからです。 +しかし `String` を使ってエラーを表すことには欠点もあります。 + + + + + + +ひとつ目の欠点は、エラーメッセージがコードのあちこちに散らかる傾向があることです。 +エラーメッセージをどこか別の場所でまとめて定義することもできますが、特別に訓練された人でない限りは、エラーメッセージをコードに埋め込むことへの誘惑に負けてしまうでしょう。 +実際、私たちは [以前の例](#code-error-double-string) でも、その通りのことをしました。 + + + + + + + +ふたつ目の、もっと重大な欠点は、 `String` への変換で *情報が欠落する* ことです。 +もし全てのエラーを文字列に変換してしまったら、呼び出し元に渡したエラーが、オペーク(不透明)になってしまいます。 +呼び出し元が `String` のエラーに対してできる唯一妥当なことは、それをユーザーに表示することだけです。 +文字列を解析して、どのタイプのエラーだったか判断するのは、もちろん強固なやり方とはいえません。 +(この問題は、ライブラリーの中の方が、他のアプケーションのようなものよりも、間違いなく重大なものになるでしょう。) + + + + + + + + + + +例えば `io::Error` 型には [`io::ErrorKind`](../std/io/enum.ErrorKind.html) が埋め込まれます。 +これは *構造化されたデータ* で、IO操作において何が失敗したのかを示します。 +エラーによって違った対応を取りたいこともあるので、このことは重要です。 +(例: あなたのアプリケーションでは `BrokenPipe` エラーは正規の手順を踏んだ終了を意味し、 `NotFound` エラーはエラーコードと共に異常終了して、ユーザーにエラーを表示することを意味するかもしれません。) +`io::ErrorKind` なら、呼び出し元でエラーの種類を調査するために、場合分けが使えます。 +これは `String` の中からエラーの詳細がなんだったのか探りだすことよりも、明らかに優れています。 + + + + + +ファイルから整数値を取り出す例で `String` をエラー型として用いた代わりに、独自のエラー型を定義し、 *構造化されたデータ* によってエラー内容を表すことができます。 +呼び出し元が詳細を検査したいときに備え、大元のエラーについての情報を取りこぼさないよう、努力してみましょう。 + + + + +*多くの可能性のうちの一つ* を表す理想的な方法は、 `enum` を使って独自の直和型を定義することです。 +このケースでは、エラーは `io::Error` もしくは `num::ParseIntError` でした。 +ここから思い浮かぶ自然な定義は: ```rust use std::io; use std::num; -// We derive `Debug` because all types should probably derive `Debug`. -// This gives us a reasonable human readable description of `CliError` values. +# // We derive `Debug` because all types should probably derive `Debug`. +# // This gives us a reasonable human readable description of `CliError` values. +// 全ての型は `Debug` を導出するべきでしょうから、ここでも `Debug` を導出します。 +// これにより `CliError` 値について、人間が十分理解できる説明を得られます。 #[derive(Debug)] enum CliError { Io(io::Error), @@ -1277,9 +1427,11 @@ enum CliError { } ``` -Tweaking our code is very easy. Instead of converting errors to strings, we -simply convert them to our `CliError` type using the corresponding value -constructor: + + + +コードの微調整はいとも簡単です。 +エラーを文字列に変換する代わりに、エラーに対応する値コンストラクタを用いて `CliError` 型に変換すればいいのです: ```rust # #[derive(Debug)] @@ -1304,17 +1456,24 @@ fn main() { } ``` -The only change here is switching `map_err(|e| e.to_string())` (which converts -errors to strings) to `map_err(CliError::Io)` or `map_err(CliError::Parse)`. -The *caller* gets to decide the level of detail to report to the user. In -effect, using a `String` as an error type removes choices from the caller while -using a custom `enum` error type like `CliError` gives the caller all of the -conveniences as before in addition to *structured data* describing the error. - -A rule of thumb is to define your own error type, but a `String` error type -will do in a pinch, particularly if you're writing an application. If you're -writing a library, defining your own error type should be strongly preferred so -that you don't remove choices from the caller unnecessarily. + + + + + + +ここでの変更点は、(エラーを文字列に変換する) `map_err(|e| e.to_string())` を、`map_err(CliError::Io)` や `map_err(CliError::Parse)` へ切り替えたことです。 +こうして *呼び出し元* が、ユーザーに対してどの程度の詳細を報告するか決められるようになりました。 +`String` をエラー型として用いることは、事実上、呼び出し元からこうした選択肢を奪ってしまいます。 +`CliError` のような独自の `enum` エラー型を用いることは、 *構造化されたデータ* によるエラーの説明だけでなく、これまでと同様の使いやすさをもたらします。 + + + + + +目安となる方法は独自のエラー型を定義することですが、 `String` エラー型も、いざというときに役立ちます。 +特にアプリケーションを書いているときなどはそうです。 +もしライブラリを書いているのなら、呼び出し元の選択肢を理由もなく奪わないために、独自のエラー型を定義することを強く推奨します。 # Standard library traits used for error handling diff --git a/TranslationTable.md b/TranslationTable.md index 95d3d326..a929c5cd 100644 --- a/TranslationTable.md +++ b/TranslationTable.md @@ -30,7 +30,7 @@ | bounds | 境界 | bug | バグ | capture | キャプチャ -| case analysis | ケース分析 +| case analysis | 場合分け | channel | チャネル | closure | クロージャ | coercion | 型強制