5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

PlaygroundAdvent Calendar 2019

Day 10

Rustのエラー処理(2)

Last updated at Posted at 2019-12-09

続編です。前編はこっち

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

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

5
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?