#12 「エラーハンドリングとResult型」_Rustを分かりたい
また別の言語に惹かれています。どちらかというと備忘録。
注意
この記事(今シリーズ)は初心者がRustをかじりながら、備忘録のような形で投稿していく予定です。
そのため、今シリーズ全体を通して信憑性は非常に低いです。
また専門の方などから見れば、無茶苦茶なこと、おかしなことをしているかもしれませんがご容赦ください。
前回
エラーハンドリングとは
言われるように、エラー発生時にどうするかの制御のこと。
Result型
rustにはresult型という便利な型があり、成功した場合と失敗した場合に分けて別の型を一つのデータとして扱える。ちなみに中身はこれ。
enum Result<T, E> {
Ok(T),
Err(E),
}
以下のように定義することで、成功と失敗の場合を一つのデータ型にできます。
Result<成功した場合の型, 失敗した場合の型>
また、返す際はOk(成功した場合の型)、失敗した場合はErr(失敗した場合の型)として返します。
使用例
例えば四則演算を行う関数を以下のように定義したとして、+-*/だったら計算ができるのでf64型で返します。その際はOk()で囲み、**とか未定義の演算子としてエラー(String型)を返します。今回はErr("未定義の演算子")。
fn calculation(operands: (f64, f64), operator: &str) -> Result<f64, String> {
match operator {
"+" => Ok(operands.0 + operands.1),
"-" => Ok(operands.0 - operands.1),
"*" => Ok(operands.0 * operands.1),
"/" => Ok(operands.0 / operands.1),
_ => Err("未定義の演算子"),
}
}
エラーコードを定義する
先ほどのようにErr(String)で返すと文字列の変更が面倒だったり、エラーが一覧できないので、どこかに集約したいです。そこでenumとResult型を使います。
enumは列挙型ともいい、
だそうです。
使用例
先ほどのコードの流用ですが、Result型もenumで定義されてます。成功と失敗が重なることはないので、一覧で定義できます。
enum Result<T, E> {
Ok(T),
Err(E),
}
エラーコードを定義
定義は簡単でこんな感じでエラーごとにエラーコードを一覧で定義できます。型を自作できる感じです。boolみたいな。
enum ErrorCode {
Error_1,
Error_2,
Error_3,
}
エラーを返す
失敗するかもしれない処理を行う関数の戻り値としてResult型のErrの方にenumであるErrorCodeを定義します。また戻り値にはOk(値)もしくはErr(値)とします。
fn hoge() -> Result<i32, ErrorCode> {
//成功したらi32型で返す
Ok(100)
//エラー1の場合は
Err(ErrorCode::Error_1)
//エラー2の場合は
Err(ErrorCode::Error_2)
}
こんな感じでErr()で囲むことでエラーとして戻します。
エラーの取り出し
関数の戻り値として返ってきたResult型から値を取り出します。
matchを利用してOkの場合とErrの場合に対してそれぞれ処理を記述できます。またOk(num)のようにすることでその後numの値を使用できます。
match hoge {
Ok(num) => println!("{}", num);
Err(error_code) => eprintln!("error: {}", error_code);
// ^^^^^^^^^^^^^^^^^^^^^^^^^^
//厳密にはエラーコードはテキストではないのでそのまま表示はできませんが後で説明するので一旦
}
エラーメッセージを表示する
Displayトレイトを使用することでprintln!("{}", hoge)のようにするだけで指定したものを表示できます。write!で指定します。
impl fmt::Display for ErrorCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
matych self {
ErrorCode::Error_1 => write!(f, "エラー1"),
ErrorCode::Error_2 => write!(f, "エラー2"),
ErrorCode::Error_3 => write!(f, "エラー3"),
}
}
}
これで先ほどのErr(error_code) => eprintln!("error: {}", error_code);で普通に表示できるようになります。
enumで型を指定
こんな感じで()で型を指定できます。
enum Solution {
Success(f64),
Failed(ErrorCode),
}
assert_eqで比較する
このままだとテストでエラーコードが一致しているか比較できないので、PartialEqトレイトを実装します。PartialEqはたった一行で実装できます。
#[derive(Debug, PartialEq)] //←だけ
enum ErrorCode {
Error_1,
Error_2,
Error_3,
}
#[test]
fn it_workd() {
assert_eq!(ErrorCodeを持った値, ErrorCodeを持った値)
}
まとめ
//定義
enum ErrorCode {
Error_1,
Error_2,
Error_3,
}
//戻す
fn hoge() -> Result<i32, ErrorCode> {
//成功したらi32型で返す
Ok(100)
//エラー1の場合は
Err(ErrorCode::Error_1)
//エラー2の場合は
Err(ErrorCode::Error_2)
}
//取り出し
match hoge {
Ok(num) => println!("{}", num);
Err(error_code) => eprintln!("error: {}", error_code);
// ^^^^^^^^^^^^^^^^^^^^^^^^^^
//厳密にはエラーコードはテキストではないのでそのまま表示はできませんが後で説明するので一旦
}
//表示用の文字を定義(Displayトレイトを定義)
impl fmt::Display for ErrorCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
matych self {
ErrorCode::Error_1 => write!(f, "エラー1"),
ErrorCode::Error_2 => write!(f, "エラー2"),
ErrorCode::Error_3 => write!(f, "エラー3"),
}
}
}
//型の指定
enum Solution {
Success(f64),
Failed(ErrorCode),
}
//assert_eqで比較
#[derive(Debug, PartialEq)] //←だけ
enum ErrorCode {
Error_1,
Error_2,
Error_3,
}
#[test]
fn it_workd() {
assert_eq!(ErrorCodeを持った値, ErrorCodeを持った値)
}