続編です。前編はこっち
Result型
こんな型です
enum Result<T, E> {
Ok(T),
Err(E),
}
Option型が、存在しないことを示すのに対し、Resultはエラーになる可能性を示します。エラーは何のために処理が失敗したのかを説明するために用いられます。もちろんエラーにNoneを渡すこともできるので、以下のように
type Option<T> = Result<T, ()>;
Result型はOption型の一般化とも言えます。Result型はOptionと同じでunwrap型があります。
impl<T, E: ::std::fmt::Debug> Result<T, E> {
fn unwrap(self) -> T {
match self {
Result::Ok(val) => val,
Result::Err(err) =>
panic!("called `Result::unwrap()` on an `Err` value: {:?}", err),
}
}
}
Option型と同じように、値が取れれば値を返し、エラーの時はエラーを起こします。Option型と違うのは、エラーに値があることで、この情報を基にDebugをしやすくします。また、型パラメーターEにはDebug制約をつけることが必要です。
整数のパース
整数をパースする処理を以下のように書いたとします。
fn double_number(number_str: &str) -> i32 {
2 * number_str.parse::<i32>().unwrap()
}
fn main() {
let n: i32 = double_number("10");
assert_eq!(n, 20);
}
文字列が整数にできない時は、
thread '<main>' panicked at 'called `Result::unwrap()` on an `Err` value: ParseIntError { kind: InvalidDigit }', /home/rustbuild/src/rust-buildbot/slave/beta-dist-rustc-linux/build/src/libcore/result.rs:729
エラーが吐き出されます。うるせぇ。標準ライブラリの parse メソッド のシグネチャをみると、
impl str {
fn parse<F: FromStr>(&self) -> Result<F, F::Err>;
}
parse型は一般化されていますが、今回はi32だけ取り扱います。FromStrはドキュメントから、FromStr→Err→ParseIntErrorと見つけていきます。それを基にOption型と同様に処理すると、
use std::num::ParseIntError;
fn double_number(number_str: &str) -> Result<i32, ParseIntError> {
match number_str.parse::<i32>() {
Ok(n) => Ok(2 * n),
Err(err) => Err(err),
}
}
fn main() {
match double_number("10") {
Ok(n) => assert_eq!(n, 20),
Err(err) => println!("Error: {:?}", err),
}
}
また場合分けをします。今回もmap型で整理しましょう
use std::num::ParseIntError;
fn double_number(number_str: &str) -> Result<i32, ParseIntError> {
number_str.parse::<i32>().map(|n| 2 * n)
}
fn main() {
match double_number("10") {
Ok(n) => assert_eq!(n, 20),
Err(err) => println!("Error: {:?}", err),
}
}
Result でよく使われるのは、unwrap_orとand_then ですResult は2つ目の型パラメータも取るので、エラー型だけに影響を与える map_err (map に相当)と or_else (and_then に相当)もあります。
Result型エイリアスを用いたイディオム
標準ライブラリではResultのような型を見ますが、Result型は二つの型パラメーターを取るように定義しました。なぜ一つだけ指定して済むようになっているのか説明する必要があります。実は、Resultの型エイリアスを定義して、エラー型を固定しています。なので、
use std::num::ParseIntError;
use std::result;
type Result<T> = result::Result<T, ParseIntError>;
fn double_number(number_str: &str) -> Result<i32> {
unimplemented!();
}
のようにして書き換ええることができます。
unwrap自体は悪じゃない
次のケースならば、unwrapは悪ではありません。
- 使い捨てのコードを書く時
- 条件にマッチしない時に致命的なバグとなるとき
続きます(だんだん疲れてきた)
補足
- Debug制約
トレイト境界の一つです。今回は人間にとって見やすい(フォーマッターによって標準出力に変換できる)型であることを示します。