Rust勉強メモ/エラー処理
パニックによるスレッド巻き戻し
「プログラミングRust 第2版」p.140
パニックの処理
1. エラーメッセージが表示される
2. 一時的な値、ローカル変数、関数の引数が逆順にドロップされる
3. 最後にスレッドが終了する
パニックでプロセスを強制終了する設定もあるらしい
パニックは安全
「プログラミングRust 第2版」p.140
Rustのどの安全性ルールにも違反しない。標準ライブラリメソッドの真ん中でパニックを起こすことができたとしても、それによって、ダングリングポインタができたり、半分だけ初期化された値がメモリ上にできたりすることはない。
std::panic::catch_unwind()でパニックを捕捉できるらしい
?プロセスが強制終了する条件
「プログラミングRust 第2版」p.141
1. 1つ目のパニックを処理中に2つ目のパニックが発生したとき
パニック処理の.drop()で2つ目のパニックが起きることがあるらしい?
2. -C panic=abortと指定したとき
Result型
「プログラミングRust 第2版」p.141
fn get_weather(location: LatLng) -> Result<WeatherReport, io::Error>
Ok(weather)かErr(error_value)
エラー処理が必ず行われる必要がある
戻り値の値(weather)を使うには、必ず何らかの処理が必要
Resultを無視すると警告
Resultのよく使うメソッド
「プログラミングRust 第2版」p.142
result.is_ok()、result.is_err()
result.ok()、result.err()
Optionで値を返す
result.unwrap_or(fallback)
失敗だった場合にデフォルト値fallbackを返す
result.unwrap_or_else(fallback_fn)
result.unwrap()、result.expect(message)
result.as_ref()、result.as_mut()
Resultの値を消費せず、参照を借用する
Result型のエイリアス
「プログラミングRust 第2版」p.143
fn remove_file(path: &Path) -> Result<()>
pub type Result<T> = result::Result<T, Error>;
エラー型の機能
「プログラミングRust 第2版」p.144
std::error::Errorトレイトは次の機能を持つ
println!()による表示
{}でエラーメッセージ、{:?}でより詳細な情報
err.to_string()
err.source()
原因となったエラーをOption型として返す
エラー原因の探求
「プログラミングRust 第2版」p.144
code:Rust
use std::error::Error;
use std::io::{Write, stderr};
fn print_error(mut err: &dyn Error) {
let _ = writeln!(stderr(), "error: {}", err);
while let Some(source) = err.source() {
let _ = writeln!(stderr(), "caused by: {}", source);
err = source;
}
}
エラー処理はGoの思想と似てる感じ
エラーの伝播
「プログラミングRust 第2版」p.145
let weather = get_weather(hometown)?;
成功→Resultの中の値を取り出す
失敗→即時リターン
try!()はRust 1.13で?が導入される前に使われたマクロ
?Optionに対する?の使用例
「プログラミングRust 第2版」p.146
fn get_weather(location: LatLng) -> Result<WeatherReport, io::Error>
let weather = get_weather(hometown).ok()?;
.ok()が無くても同じ挙動なのでは?
get_weather()が成功→.ok()はSome
get_weather()が失敗→.ok()はNone
複数種類のエラー
「プログラミングRust 第2版」p.146
code:Rust
use std::io::{self, BufRead};
fn read_numbers(file: &mut dyn BufRead) -> Result<Vec<i64>, io::Error> {
let mut numbers = vec![];
for line_result in file.lines() {
let line = line_result?; // ここは std::io::Error
numbers.push(line.parse()?); // ここは std::num::ParseIntError
}
Ok(numbers)
}
line.parse()のエラー型std::num::ParseIntErrorをstd::io::Errorに変換できない
複数種類のエラーへの対応
「プログラミングRust 第2版」p.146
1. 独自のエラー型を定義し、2種類のエラーをラップする
thiserrorクレートというものがあるらしい
2. すべてのエラーを表せる型に変換する
type GenericError = Box<dyn std::error::Error + Send + Sync + 'static>
dyn std::error::Error→任意のエラーを表す型
Send + Sync + 'static→スレッド間で受け渡せるようにする制約
?演算子の型変換
「プログラミングRust 第2版」p.147
?がio::Error等をGenericErrorに変換してくれる
手動で変換することもできる
code:Rust
let io_error = io::Error::new(io::ErrorKind::Other, "timed out");
return Err(GenericError::from(io_error));
Fromトレイトというものを使っているらしい
期待するエラーかどうかの検査
「プログラミングRust 第2版」p.148
type GenericResult<T> = Result<T, GenericError>;を返すと、関数の呼び出し側では全てのエラーに対処しなければならなくなる
.downcast_ref::<希望のエラー型>()を使うと、指定したエラー型かどうかを検査できる
code:Rust
loop {
match compile_project() {
Ok(()) => return Ok(()),
Err(err) => {
if let Some(mse) = err.downcast_ref::<MissingSemicolonError>() {
insert_semicolon_in_source_code(mse.file(), mse.line())?;
continue;
}
return Err(err);
}
}
}
起こるはずのないエラー
「プログラミングRust 第2版」p.148
digitsは、0~9の数字からなる文字列であることが検査により確定しているとする
digits.parse::<u64>()は必ず成功するはず
let num = digits.parse::<u64>().unwrap();で良さそう
もしdigitsがu64に収まらない大きな数値が入りうる文脈かもしれないなら、unwrap()を使うのはバグと言える
エラーの無視
「プログラミングRust 第2版」p.149
print_error()関数の中で何らかのエラーが発生しても無視したい
元々printしようとしていたエラーの方が重要だから
例えば、stderrを他のプロセスにパイプでつないで出力していて、そのプロセスが殺されてしまったような場合だ。
Resultを_に代入すれば、Resultを使っていない警告を抑制できる
main関数の型
main()の返り値の型は通常はResultではない
「プログラミングRust 第2版」p.150
Resultを戻り値とするmain()を作ることもできる
code:Rust
fn main() -> Result<(), TideCalcError> {
let tides = calculate_tides()?;
print_tides(tides);
Ok(())
}
エラー型は{:?}で表示できるものなら何でも可
独自のエラー型
「プログラミングRust 第2版」p.151
独自のエラー型を標準のエラー型と同じように扱えるようにするにはfmt::Displayとstd::error::Errorを実装する
thiserrorクレートを使うと便利
code:Rust
use thiserror::Error;
pub struct JsonError {
message: String,
line: usize,
column: usize,
}
Goのエラー処理との比較
code:Goでのエラー処理.go
f, err := os.Open("foo.txt")
if err != nil {
return err
}
code:Rustでのエラー処理.rs
let mut f = File::open("foo.txt")?;
Result vs 例外
「プログラミングRust 第2版」p.152
Resultを使う方式では、失敗する可能性の有無がすぐ分かる
例外方式は、その関数が例外を出すか不明
特にC++の例外の仕様は……
プログラマにエラーに対処することを要求する
明示的に無視するなり、伝播するなり、印字するなり
最も一般的な対処「伝播」は?で可能