Closed8
The Rust Programming Language - Chapter 9 メモ
エラー処理
- Rust でのエラーの分類
- 回復可能エラー
- ファイル見つからない、など
-
Resut<T, E>
値
- 回復不能エラー
- バグの兆候
-
panic!
マクロ
- 回復可能エラー
- Rust には例外が存在しない
panic!で回復不能なエラー
panic
発生時の振る舞い
- 巻き戻し(デフォルト)
- スタックを遡り、遭遇した各関数のデータを片付ける
- やることが多くなる
- 異常終了
- 片付けをせずにプログラムを終了させる
- プログラムが使用していたメモリはOSが片付ける必要がある
# RUST_BACKTRACE=1 を追加すると、バックトレースを出力できる
RUST_BACKTRACE=1 cargo run
Resultで回復可能なエラー
Resut型のおさらい
Rust
enum Result<T, E> {
Ok(T),
Err(E),
}
match 式を使用して Ok, Err を処理する
Rust
use std::fs::File;
fn main() {
let f = File::open("hello.txt");
let f = match f {
Ok(file) => file,
Err(error) => {
panic!("There was a problem opening the file: {:?}", error)
}
};
}
色々なエラーにマッチする
- 失敗理由によって動作を変える
- ファイルが存在しないためにエラー -> ファイルを作成
- Read権限がないためにエラー -> panic!する
Rust
use std::fs::File;
use std::io::ErrorKind;
fn main() {
let f = File::open("hello.txt");
let _f = match f {
Ok(file) => file,
// io::Error 構造体には kind メソッドがあり、io::ErrorKind enum を返す
// if error.kind() == ErrorKind::NotFound はマッチガード
// ref error としているのは error がガード条件式にムーブされないようにするため
// & ではなく ref Ni
Err(ref error) if error.kind() == ErrorKind::NotFound => {
match File::create("hello.txt") {
Ok(fc) => fc,
Err(e) => {
panic!("Tried to create file but there was a problem: {:?}", e)
},
}
},
Err(error) => {
panic!("There was a problem opening the file: {:?}", error)
}
};
}
エラー時にパニックするショートカット: unwrapとexpect
Result<T, E>
のヘルパーメソッド unwrap()
- Result値がOk列挙子なら、unwrapはOkの中身を返す
- Result値がErr列挙子なら、unwrapはpanic!マクロを呼ぶ
Rust
use std::fs::File;
fn main() {
let f = File::open("hello.txt").unwrap();
}
expect()
メソッド
-
unwrap()
とほぼ同じだが、パニック時のエラーメッセージを指定できるので、パニックの原因をたどりやすくなる。
ということは、常にunwrap()
よりexpect()
使ったほうがよい??
Rust
use std::fs::File;
fn main() {
let f = File::open("hello.txt").expect("Failed to open hello.txt");
}
エラーを委譲する
Rust
use std::io;
use std::io::Read; // read_to_string するのに必要
use std::fs::File;
fn read_username_from_file() -> Result<String, io::Error> {
let f = File::open("hello.txt");
let mut f = match f {
Ok(file) => file,
// return することで read_username_from_file の戻り値を返す
Err(e) => return Err(e),
};
let mut s = String::new();
match f.read_to_string(&mut s) {
Ok(_) => Ok(s),
// こちらは関数の最後なので明示的な return はいらない
Err(e) => Err(e),
}
}
fn main() {
// 呼び出し側でハンドルする
let f = read_username_from_file().expect("Something went wrong");
}
エラー委譲のショートカット: ?演算子
エラーをより簡潔に書くために ?
を使う
-
?
はmatch
とは違う -
?
はfrom
関数を呼び出す-
from
は、エラーの型を、現在の関数の戻り値型で定義されているエラー型に変換する - 下の例で言うと、open 失敗も、read_to_string失敗も
io::Error
として返される
-
Rust
fn read_username_from_file() -> Result<String, io::Error> {
let mut f = File::open("hello.txt")?;
let mut s = String::new();
f.read_to_string(&mut s)?;
Ok(s)
}
?
を連結させてさらに簡潔に書くことができる
Rust
fn read_username_from_file() -> Result<String, io::Error> {
let mut s = String::new();
// ES2020 の Optional Chaining のようだ
File::open("hello.txt")?.read_to_string(&mut s)?;
Ok(s)
}
?
の制約として、Result型を返す関数でしか使えない
Rust
use std::fs::File;
// これはダメ
fn main() {
let f = File::open("hello.txt")?;
}
panic!すべきかするまいか
-
panic!
すべき時- パニックしたら回復する手段はない
- 稀に、プロトタイプコード、テストではパニックの方が適していることもある。
-
Resut
を返すべき時- 失敗する可能性のある関数を定義する際には、良い第一選択肢となる
例、プロトタイプコード、テスト
同様に、unwrapやexpectメソッドは、エラーの処理法を決定する準備ができる前、プロトタイプの段階では、 非常に便利です。それらにより、コードにプログラムをより頑健にする時の明らかなマーカーが残されるわけです。
なるほど、確かにいちいちエラーハンドリングするよりも unwrap
などで処理してどんどんコードを書いていくほうがよさそう。
コンパイラよりもプログラマがより情報を持っている場合
use std::net::IpAddr;
// プログラマはこれは問題なく処理できるとわかっているとき unwrap することは受容される
let home: IpAddr = "127.0.0.1".parse().unwrap();
エラー処理のガイドライン
コードが悪い状態に陥る可能性があるときにパニックさせるのは、推奨されることです。この文脈において、 悪い状態とは、何らかの前提、保証、契約、不変性が破られたことを言い、例を挙げれば、無効な値、 矛盾する値、行方不明な値がコードに渡されることと、
↑一応 panic
の妥当性は主張しつつ
しかし、どんなにコードをうまく書いても起こると予想されますが、悪い状態に達したとき、それでもpanic!呼び出しをするよりも、 Resultを返すほうがより適切です。
↑基本的にはResult
を返すほうが良いとの主張。
このスクラップは2021/07/10にクローズされました