Rust でのエラーハンドリングいろいろ
CREATED: 2023 / 08 / 09 Wed
UPDATED: 2023 / 08 / 09 Wed
Rust での Results
を使ったエラーハンドリング
Results
は Ok
と Err
を返す enum ですが、一般的にこれを使ってエラーを取り扱います、rust では。
例として、std::fs::read_to_string という関数があるのですが、これは指定されたパスが存在しない場合は Err
を返し、正常に処理が終了すれば Ok
を返します。
// main.rs
fn main() {
let result = std::fs::read_to_string("test.txt");
match result {
Ok(content) => { println!("File content: {}", content); }
Err(error) => { println!("Oh noes: {}", error); }
}
}
$ rustc main.rs
$ ./main
Oh noes: No such file or directory (os error 2)
こんな感じでファイルのパスが存在しなければエラーを吐きます。
エラーは吐きますが、match
の後に書いた処理は実行されます。
以上終了させたいのであれば panic!
させます。
match result {
Ok(content) => { println!("File content: {}", content); }
Err(error) => { panic!("Oh noes: {}", error); }
}
$ ./main
thread 'main' panicked at 'Oh noes: No such file or directory (os error 2)', main.rs:5:23
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
そしたら match
の後に書いた処理は実行されません。
match
から結果を取り出したい
match
の中でファイルの中身を取得できても、その外で扱いたいことがほとんどだと思います。
Ok の中で処理をするので十分な場合はこのままで良いですが、取得した中身を取り出してみましょう。
let result = std::fs::read_to_string("test.txt");
let content = match result {
Ok(content) => { content },
Err(error) => { panic!("Can't deal with {}, just exit here", error); }
};
println!("file content: {}", content);
こうすれば content
に中身が渡って下の println!
でその内容が表示されるようになります。
もちろん Err
の場合は異常終了します。
または、このコードは以下のように書いても同じ挙動をとります。
let content = std::fs::read_to_string("test.txt").unwrap();
println!("file content: {}", content);
すっきりしました。 お好みでどうぞ。
でも panic!
させたくない
場合によってはエラーが発生しても異常終了せずに、後続の処理に結果を活かしたい場合もあるかもしれません。
以下のようにすると、この処理が実行される関数から Err
が返却されます。
let result = std::fs::read_to_string("test.txt");
let content = match result {
Ok(content) => { content },
Err(error) => { return Err(error.into()); }
};
するとこのコードをコンパイルする時に return type
の問題で怒られるかもしれません。
Err
なんて返すなと。
そしたらこうしてあげれば良いです。
fn main() -> Result<(), Box<dyn std::error::Error>> {
let result = std::fs::read_to_string("test.txt");
let content = match result {
Ok(content) => { content },
Err(error) => { return Err(error.into()); }
};
println!("file content: {}", content);
Ok(())
}
return type
に Result
を指定してあげれば、Err
が返ろうが Ok
が返ろうが問題ありません。
ファイルの内容が取得されれば println!
で出力されますし、とりあえずこれで良さそうです。
これでこの関数の戻り値を受け取る側で何かしらの処理を加えることができるでしょう。
ちなみに最終行の Ok(())
は return Ok(())
の短縮形です。
クエスチョンマークを使った panic!
回避
もっと簡潔に書きたい場合はクエスチョンマークを使った記法もあります。
fn main() -> Result<(), Box<dyn std::error::Error>> {
let content = std::fs::read_to_string("test.txt")?;
println!("file content: {}", content);
Ok(())
}
これも同様に問題があれば Err
を返却し、正常に終了すれば Ok(())
が返却されます。
エラー内容にコンテクストを付け加えたい
エラーがどのような理由で発生したのか、その前後関係を補足したい時があります。 上記のコードを実行したときも Not Found エラーが出ているものの、じゃあ何が Not Found になっているのかがわからなかったかと思います。 こういった具体的な問題の内容を知ることができれば大変嬉しいものです。
そんな時にはカスタムのエラー構造体を作りましょう。
struct CustomError(String);
こんな感じのカスタムエラー構造体を以下のように利用すれば、エラーの内容をより具体的に表現することができます。
fn main() -> Result<(), CustomError> {
let path = "test.txt";
let content = std::fs::read_to_string(path)
.map_err(|err| CustomError(format!("Error reading `{}`: {}", path, err)))?;
println!("file content: {}", content);
Ok(())
}
Error: CustomError("Error reading `test.txt`: No such file or directory (os error 2)")
このカスタムエラー構造体を定義してエラーメッセージを具体的に出力するマナーはよくあるらしいです。 ただまあ、より CLI ツールのエラーっぽく出力したい場合は、anyhow というライブラリを使うことでいい感じにしてくれます。
anyhow の Context
トレイトによって出力がいい感じに見通しよく表示されます。
use anyhow::{Context, Result};
fn main() -> Result<()> {
let path = "test.txt";
let content = std::fs::read_to_string(path)
.with_context(|| format!("could not read file `{}`", path))?;
println!("file content: {}", content);
Ok(())
}
Error: could not read file `test.txt`
Caused by:
No such file or directory (os error 2)
を仕舞い
このページのを砕いて書きました。