Closed8

The Rust Programming Language - Chapter 9 メモ

Tatsushi KiryuTatsushi Kiryu

https://doc.rust-jp.rs/book-ja/ch09-01-unrecoverable-errors-with-panic.html

panic!で回復不能なエラー

panic 発生時の振る舞い

  • 巻き戻し(デフォルト)
    • スタックを遡り、遭遇した各関数のデータを片付ける
    • やることが多くなる
  • 異常終了
    • 片付けをせずにプログラムを終了させる
    • プログラムが使用していたメモリはOSが片付ける必要がある
# RUST_BACKTRACE=1 を追加すると、バックトレースを出力できる
RUST_BACKTRACE=1 cargo run
Tatsushi KiryuTatsushi Kiryu

色々なエラーにマッチする

  • 失敗理由によって動作を変える
    • ファイルが存在しないためにエラー -> ファイルを作成
    • 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)
        }
    };
}
Tatsushi KiryuTatsushi Kiryu

エラー時にパニックするショートカット: 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");
}
Tatsushi KiryuTatsushi Kiryu

エラーを委譲する

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");
}
Tatsushi KiryuTatsushi Kiryu

エラー委譲のショートカット: ?演算子

エラーをより簡潔に書くために ? を使う

  • ?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")?;
}
Tatsushi KiryuTatsushi Kiryu

https://doc.rust-jp.rs/book-ja/ch09-03-to-panic-or-not-to-panic.html

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にクローズされました