百名山 API を開発しました | mountix APICLICK !

Rust | エラーハンドリング – Result と Option を上手に扱いたい

Rust Error Handling - Result / Option
  • URLをコピーしました!

unwrapやらexpectやら… いろいろあるので、まとめてみました。

型推論が効く部分も、返り値の型を分かりやすくするために定義しています。

目次

Result 型のエラーハンドリング

Stirngu32に型変換するparse::<u32>()を使って、エラーハンドリングしていきます。

use std::env;
use std::num::ParseIntError;

fn main() {
    let text = env::args().nth(1).expect("Please specify a text.");

    let parsed: Result<u32, ParseIntError> = text.parse::<u32>();

    match parsed {
        Ok(number) => println!("{}", number), // <- u32
        Err(err) => eprintln!("{}", err), // <- ParseIntError
    }
}
$ cargo run -- 100
100

$ cargo run -- error
invalid digit found in string

expect

Okの場合はTを返し、Errの場合はメッセージとして&strを返します。

use std::env;
use std::num::ParseIntError;

fn main() {
    let text = env::args().nth(1).expect("Please specify a text.");

    let number: u32 = text.parse::<u32>().expect("Error!");

    println!("{}", number);
}
$ cargo run -- 100
100

$ cargo run -- error
thread 'main' panicked at 'Error!: ParseIntError { kind: InvalidDigit }', src/main.rs:7:43

expect_err

Okの場合に panic が発生します。OK 時に出力するメッセージ &strは必須です。

Errの場合、panic は起きないため正常終了します。

use std::env;
use std::num::ParseIntError;

fn main() {
    let text = env::args().nth(1).expect("Please specify a text.");

    let parsed: ParseIntError = text.parse::<u32>().expect_err("OK!");

    println!("{:?}", parsed);
}
$ cargo run -- 100
thread 'main' panicked at 'OK!: 100', src/main.rs:7:53

$ cargo run -- error
ParseIntError { kind: InvalidDigit }

unwrap

Okの場合はTを返し、Errの場合は panic が発生します。

use std::env;
use std::num::ParseIntError;

fn main() {
    let text = env::args().nth(1).expect("Please specify a text.");

    let number: u32 = text.parse::<u32>().unwrap();

    println!("{}", number);
}
$ cargo run -- 100
100

$ cargo run -- error
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: ParseIntError { kind: InvalidDigit }', src/main.rs:7:43

unwrap_or

Okの場合はTを返し、Errの場合はdefault: Tを返します。

以下のコードではデフォルトの値として10u32を渡しています。

use std::env;
use std::num::ParseIntError;

fn main() {
    let text = env::args().nth(1).expect("Please specify a text.");

    let number: u32 = text.parse::<u32>().unwrap_or(10u32);

    println!("{}", number);
}
$ cargo run -- 100
100

$ cargo run -- error
10

unwrap_or_else

Okの場合、引数のクロージャを実行して結果の値を返します。

use std::env;
use std::num::ParseIntError;

fn main() {
    let text = env::args().nth(1).expect("Please specify a text.");

    let number: u32 = text.parse::<u32>().unwrap_or_else(|e| {
        eprintln!("{}", e);
        10u32
    });

    println!("{}", number);
}
$ cargo run -- 100
100

$ cargo run -- error
invalid digit found in string
10

以下のように書くことも可能です。

use std::env;
use std::num::ParseIntError;

fn main() {
    let text = env::args().nth(1).expect("Please specify a text.");

    let number: u32 = text.parse::<u32>().unwrap_or_else(|_| 10u32);

    println!("{}", number);
}
$ cargo run -- 100
100

$ cargo run -- error
10

unwrap_orunwrap_or_else

unwrapする対象が関数である場合、遅延評価されるunwrap_or_elseを使うことが推奨されています。

unwrap_orに渡された引数は先行評価されます。

unwrap_or_default

Errの場合、デフォルトの値が返ります。

use std::env;
use std::num::ParseIntError;

fn main() {
    let text = env::args().nth(1).expect("Please specify a text.");

    let number: u32 = text.parse::<u32>().unwrap_or_default();

    println!("{}", number);
}

今回はu32なので、デフォルト値の0が出力されます。

$ cargo run -- 100
100

$ cargo run -- error
0

ok

Option<T>、ここではOption<String>に変換できます。

use std::env;
use std::num::ParseIntError;

fn main() {
    let text = env::args().nth(1).expect("Please specify a text.");

    let parsed: Option<u32> = text.parse::<u32>().ok();

    println!("{:?}", parsed);
}
$ cargo run -- 100
Some(100)

$ cargo run -- error
None

err

Option<Error>、ここではOption<ParseIntError>変換できます。

use std::env;
use std::num::ParseIntError;

fn main() {
    let text = env::args().nth(1).expect("Please specify a text.");

    let parsed: Option<ParseIntError> = text.parse::<u32>().err();

    println!("{:?}", parsed);
}
$ cargo run -- 100
None

$ cargo run -- error
Some(ParseIntError { kind: InvalidDigit })

and

結果がOkであればOk<T>を返し、それ以外の場合はselfErrを返します。

use std::env;
use std::num::ParseIntError;

fn main() {
    let text = env::args().nth(1).expect("Please specify a text.");

    let parsed: Result<String, ParseIntError> = text.parse::<u32>().and(Ok("Success!".to_string()));

    println!("{:?}", parsed);
}
$ cargo run -- 100
Ok("Success!")

$ cargo run -- error
Err(ParseIntError { kind: InvalidDigit })

and_then

結果がOkの場合は引数で渡した関数を実行し、それ以外の場合はselfErr値を返します。

use std::env;
use std::num::ParseIntError;

fn main() {
    let text = env::args().nth(1).expect("Please specify a text.");

    let parsed: Result<u32, ParseIntError> = text.parse::<u32>().and_then(double);

    println!("{:?}", parsed);
}

fn double(num: u32) -> Result<u32, ParseIntError> {
    Ok(num * 2)
}
$ cargo run -- 100
Ok(200)

$ cargo run -- error
Err(ParseIntError { kind: InvalidDigit })

https://doc.rust-jp.rs/rust-by-example-ja/error/option_unwrap/and_then.html

? 演算子

Result<T, E>を返す関数では、?演算子を使うことができます。

use std::num::ParseIntError;

fn main() -> Result<(), ParseIntError> {
    let text = "error".to_string();

    let parsed: u32 = text.parse::<u32>()?;

    println!("{:?}", parsed);

    Ok(())
}
$ cargo run -- 100
100

$ cargo run -- error
Error: ParseIntError { kind: InvalidDigit }

map / map_err

map()Ok(T)の場合のみ実行され、内部クロージャがTを返すとmap()Ok(T)をラップします。

クロージャではErrになり得る処理は使用できません。

use std::env;
use std::num::ParseIntError;

fn main() {
    let text = env::args().nth(1).expect("Please specify a text.");

    println!("{:?}", double(&text))
}

fn double(text: &str) -> Result<u32, ParseIntError> {
    text.parse::<u32>().map(|num| num * 2)
}
$ cargo run -- 100
Ok(200)

$ cargo run -- error
Err(ParseIntError { kind: InvalidDigit })

map_err()を組み合わせて、Errの場合はErr(String)を返すようにしてみます。

map_err()では内部クロージャがTを返すとmap_err()Err(T)をラップします。

use std::env;
use std::num::ParseIntError;

fn main() {
    let text = env::args().nth(1).expect("Please specify a text.");

    println!("{:?}", double(&text))
}

fn double(text: &str) -> Result<u32, String> {
    text.parse::<u32>().map(|num| num * 2).map_err(|e| e.to_string())
}
$ cargo run -- 100
Ok(200)

$ cargo run -- error
Err("invalid digit found in string")

is_ok

Okの場合、bool型でtrueを返します。

use std::env;
use std::num::ParseIntError;

fn main() {
    let text = env::args().nth(1).expect("Please specify a text.");

    // 戻り値の型は bool
    if text.parse::<u32>().is_ok() {
        println!("{}", "Success!");
    }
}
$ cargo run -- 100
Success!

is_err

Errの場合、bool型でtrueを返します。

use std::env;
use std::num::ParseIntError;

fn main() {
    let text = env::args().nth(1).expect("Please specify a text.");

    // 戻り値の型は bool
    if text.parse::<u32>().is_err() {
        println!("{}", "Error!");
    }
}
$ cargo run -- error
Error!

transpose

Result<Option<T>, E>Option<Result<T, E>>に変換します。

use std::env;

fn main() {
    let text = env::args().nth(1).expect("Please specify an text.");

    let parsed: Option<Result<u32, ()>> = parse_u32(&text).transpose();

    println!("{:?}", parsed);
}

fn parse_u32(text: &str) -> Result<Option<u32>, ()> {
    match text.parse::<u32>() {
        Ok(num) => {
            if num > 10 {
                Ok(Some(num))
            } else {
                Ok(None)
            }
        }
        Err(_) => Err(()),
    }
}
$ cargo run -- 100
Some(Ok(100))

$ cargo run -- 1
None

$ cargo run -- error
Some(Err(()))

if let 構文

OkもしくはErr場合のみ処理したいはif let構文を使うこともできます。

if let Some(..)

use std::env;
use std::num::ParseIntError;

fn main() {
    let text = env::args().nth(1).expect("Please specify a text.");

    if let Ok(number) = text.parse::<u32>() {
        println!("{}", number);
    }
}
$ cargo run -- 100
100

if let Err(..)

use std::env;
use std::num::ParseIntError;

fn main() {
    let text = env::args().nth(1).expect("Please specify a text.");

    if let Err(err) = text.parse::<u32>() {
        eprintln!("{}", err);
    }
}
$ cargo run -- error
invalid digit found in string

Option 型のエラーハンドリング

環境変数から値を取得することができるenv::var_osを使って、エラーハンドリングをしていきます。

use std::env;
use std::ffi::OsString;

fn main() {
    let key = "TEST".to_string();
    let s: Option<OsString> = env::var_os(&key);

    println!("{:?}", s);
}
$ TEST=test cargo run
Some("test")

$ ERROR=error cargo run
None

expect

Someの場合はTを返し、Errの場合はメッセージとして&strを返します。

use std::env;
use std::ffi::OsString;

fn main() {
    let key = "TEST".to_string();
    let s: OsString = env::var_os(&key).expect("none");

    println!("{:?}", s);
}
$ TEST=test cargo run
"test"

$ ERROR=error cargo run
thread 'main' panicked at 'none', src/main.rs:6:41

unwrap

Someの場合はTを返し、Noneの場合は panic が発生します。

use std::env;
use std::ffi::OsString;

fn main() {
    let key = "TEST".to_string();
    let s: OsString = env::var_os(&key).unwrap();

    println!("{:?}", s);
}
$ TEST=test cargo run
"test"

$ ERROR=error cargo run
thread 'main' panicked at 'called `Option::unwrap()` on a `None` value', src/main.rs:6:41

unwrap_or

Someの場合はTを返し、Noneの場合はdefault: Tを返します。

use std::env;
use std::ffi::OsString;

fn main() {
    let key = "TEST".to_string();
    let s: OsString = env::var_os(&key).unwrap_or(OsString::from("undefined"));

    println!("{:?}", s);
}
$ TEST=test cargo run
"test"

$ ERROR=error cargo run
"undefined"

unwrap_or_else

Noneの場合、引数のクロージャを実行して結果の値を返します。

use std::env;
use std::ffi::OsString;

fn main() {
    let key = "TEST".to_string();
    let s: OsString = env::var_os(&key).unwrap_or_else(|| OsString::from("undefined"));

    println!("{:?}", s);
}
$ TEST=test cargo run
"test"

$ ERROR=error cargo run
"undefined"

unwrap_or_default

Noneの場合、デフォルトの値が返ります。

use std::env;
use std::ffi::OsString;

fn main() {
    let key = "TEST".to_string();
    let s: OsString = env::var_os(&key).unwrap_or_default();

    println!("{:?}", s);
}
$ TEST=test cargo run
"test"

$ ERROR=error cargo run
""

ok_or

Option<T>Result<T, E>に変換します。

use std::env;
use std::ffi::OsString;

fn main() {
    let key = "TEST".to_string();

    let s: Result<OsString, String> = env::var_os(&key).ok_or("undefined".to_string());

    println!("{:?}", s);
}
$ TEST=test cargo run
Ok("test")

$ ERROR=error cargo run
Err("undefined")

ok_or_else

Option<T>Result<T, E>に変換します。

use std::env;
use std::ffi::OsString;

fn main() {
    let key = "TEST".to_string();
    let s: Result<OsString, String> = env::var_os(&key).ok_or_else(|| "undefined".to_string());

    println!("{:?}", s);
}
$ TEST=test cargo run
Ok("test")

$ ERROR=error cargo run
Err("undefined")

and

結果がSomeであればSome<T>を返し、それ以外の場合はNoneを返します。

use std::env;

fn main() {
    let key = "TEST".to_string();
    let s: Option<String> = env::var_os(&key).and(Some("Success!".to_string()));

    println!("{:?}", s);
}
$ TEST=test cargo run
Some("Success!")

$ ERROR=error cargo run
None

and_then

結果がSomeの場合は引数で渡した関数を実行し、それ以外の場合はselfNoneを返します。

use std::env;
use std::ffi::OsString;

fn main() {
    let key = "TEST".to_string();
    let s: Option<String> = env::var_os(&key).and_then(change_to_string);

    println!("{:?}", s);
}

fn change_to_string(target: OsString) -> Option<String> {
    match target.into_string() {
        Ok(s) => Some(s),
        Err(_) => None,
    }
}
$ TEST=test cargo run
Some("test")

$ ERROR=error cargo run
None

? 演算子

Option<T>を返す関数では、?演算子を使うことができます。

se std::env;
use std::ffi::OsString;

fn main() {
    let key = "TEST".to_string();
    let s: Option<String> = env::var_os(&key).and_then(change_to_string);

    println!("{:?}", s);
}

fn change_to_string(target: OsString) -> Option<String> {
    let validated =match target.into_string() {
        Ok(s) => Some(s),
        Err(_) => None,
    };

    let result = validated?; // return Option<String>::None
    Some(result)
}
$ TEST=test cargo run
Some("test")

$ ERROR=error cargo run
None

map

map()Some(T)の場合のみ実行され、内部クロージャがTを返すとmap()Some(T)をラップします。

use std::env;

fn main() {
    let key = "TEST".to_string();
    let s: Option<String> = env::var_os(&key).map(|s| {
        match s.into_string() {
            Ok(s) => s,
            Err(_) => "failed".to_string()
        }
    });

    println!("{:?}", s);
}
$ TEST=test cargo run
Some("test")

$ TEST_ERROR=error cargo run
None

map_or

Someの場合は関数Fの実行結果、Noneの場合はdefault: Uが返ります。

デフォルト値に関数の返り値を使用する場合は遅延評価されるmap_or_elseの使用が推奨されています。

use std::env;
use std::ffi::OsString;

fn main() {
    let key = "TEST".to_string();
    let s: String = env::var_os(&key).map_or("default".to_string(), change_to_string);

    println!("{:?}", s);
}

fn change_to_string(target: OsString) -> String {
    match target.into_string() {
        Ok(s) => s,
        Err(_) => "failed".to_string(),
    }
}
$ TEST=test cargo run
"test"

$ TEST_ERROR=error cargo run
"default"

map_or_else

Someの場合は関数Fの実行結果、Noneの場合は関数Dの実行結果が返ります。

use std::env;
use std::ffi::OsString;

fn main() {
    let key = "TEST".to_string();
    let s: String = env::var_os(&key).map_or_else(default_string, change_to_string);

    println!("{:?}", s);
}

fn change_to_string(target: OsString) -> String {
    match target.into_string() {
        Ok(s) => s,
        Err(_) => "failed".to_string(),
    }
}

fn default_string() -> String {
    "default".to_string()
}
$ TEST=test cargo run
"test"

$ TEST_ERROR=error cargo run
"default"

is_some

Someの場合、bool型でtrueを返します。

use std::env;
use std::ffi::OsString;

fn main() {
    let key = "TEST".to_string();
    if env::var_os(&key).is_some() {
        println!("Some!");
    }
}
$ TEST=test cargo run
Some!

is_none

Noneの場合、bool型でtrueを返します。

use std::env;
use std::ffi::OsString;

fn main() {
    let key = "TEST".to_string();
    if env::var_os(&key).is_none() {
        println!("None!");
    }
}
$ TEST_ERROR=error cargo run
None!

transpose

Option<Result<T, E>>Result<Option<T>, E>に変換します。

use std::env;
use std::ffi::OsString;

fn main() {
    let key = "TEST".to_string();
    let s = env::var_os(&key).expect("TEST is undefined.");

    let result: Result<Option<Strirng>, ()> = change_to_string(s).transpose();
    println!("{:?}", result);
}


fn change_to_string(target: OsString) -> Option<Result<String, ()>> {
    match target.into_string() {
        Ok(s) => {
            if !s.is_empty() {
                Some(Ok(s))
            }else {
                Some(Err(()))
            }
        },
        Err(_) => None,
    }
}
$ TEST=test cargo run
Ok(Some("test"))

$ TEST="" cargo run
Err(())

if let 構文

SomeもしくはNone場合のみ処理したいはif let構文を使うこともできます。

if let Some(..)

use std::env;

fn main() {
    let key = "TEST".to_string();
    if let Some(s) = env::var_os(&key) {
        println!("{:?}", s);
    }
}
$ TEST=test cargo run
"test"

if None

use std::env;

fn main() {
    let key = "TEST".to_string();
    if env::var_os(&key) == None {
        println!("None");
    }
}
$ TEST_ERROR=error cargo run
None

is_noneがあるし、使いどころはあまりなさそう…?

まとめ

どれを使うかはケースバイケースでしょうし、その勘所は実装しながら身につける必要がありますね。

実際に手を動かすと理解が深まった気がします!

著:初田直也, 著:山口聖弘, 著:吉川哲史, 著:豊田優貴, 著:松本健太郎, 著:原将己, 著:中村謙弘, 著:フォルシア株式会社
¥3,208 (2022/12/01 07:19時点 | Amazon調べ)
Rust Error Handling - Result / Option

この記事が気に入ったら
フォローしてね!

  • URLをコピーしました!

コメント

コメントする

目次