Help us understand the problem. What is going on with this article?

Rustのエラー処理(2)

続編です。前編はこっち

Rustのエラー処理(1)

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

トレイト境界の一つです。今回は人間にとって見やすい(フォーマッターによって標準出力に変換できる)型であることを示します。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした