開発

[binllion] エラーハンドリング

エラー処理を行う

ここでunwrap()について考えてみましょう。

このコードの中でunwrap()が使われているのは下記の3箇所です。

enable_raw_mode().unwrap();
let input_byte = input_byte.unwrap();
disable_raw_mode().unwrap();Code language: JavaScript (javascript)

enable_raw_mode()のドキュメントを見ると、enable_raw_mode()はResultという型を返す関数になっています。他の2箇所も同様です。つまりunwrap()はResultのメソッドを呼び出していることになります。

では、Resultとは何でしょうか!?こちらもドキュメントに書いてあります

Resultは列挙型で成功を示すOk()か失敗を示すErr()のどちらかがセットされる型になります。他の言語では例外処理をtry文等で書いたりしますがRustではこのResultを使ってエラー処理を行います。

unwrap()はこのResultを処理するための一つの方法ですが他にも様々な処理の仕方があります。どのような方法があるのか少し見てみましょう。

様々なエラー処理を試してみる

input_byteの処理コードを切り出してそれぞれのResult処理を試してみましょう。

unwrap()

unwrap()はResultがOk()であればその値を取り出し、Err()であればパニックして終了します。

もし回復可能なエラーであれば終了してしまうのは問題があります。unwrap_or()等、パニックを起こさないメソッドもありますので別の方法を検討すべきでしょう。

#![allow(unused)]
use std::io::Error;

fn main() {
    let input_byte: std::result::Result<&str, Error> = Ok("This is ok");
    let input_byte = input_byte.unwrap();
    println!("{}", input_byte);

    let input_byte: std::result::Result<&str, Error> = Err(Error::other("This is Error"));
    let input_byte = input_byte.unwrap();
    println!("{}", input_byte); 
   
    println!("End of main()");
}
Code language: JavaScript (javascript)

playground

expect()

Err時にメッセージを追加できるというところがunwrap()との大きな違いでしょうか。それ以外はunwrap()と同じくパニックしてしまうので別の方法を検討すべきでしょう。

#![allow(unused)]
use std::io::Error;

fn main() {
    let input_byte: std::result::Result<&str, Error> = Ok("This is ok");
    let input_byte = input_byte.expect("This is expect");
    println!("{}", input_byte);

    let input_byte: std::result::Result<&str, Error> = Err(Error::other("This is Error"));
    let input_byte = input_byte.expect("This is expect");
    println!("{}", input_byte); 
   
    println!("End of main()");
}
Code language: JavaScript (javascript)

playground

match式

他の言語ではswitch文が同等になると思います。

この例ではinput_byte1がOkの場合、Errの場合、それ以外の場合(アンダーバー)をそれぞれ記述しています。

match式の特徴としてはとりうる全てのパターンを網羅しなければならない、ということです。ResultはOkかErrのいずれかの値になるので、match式にOkだけの記述をしてもコンパイルエラーとなります。

アンダーバーはワイルドカードのようなもので全てにマッチします2。例えばinput_byte の型がResultではなくu8型だった場合、u8がとりうる数字の範囲は0-255になります。256パターンも記述していたら大変ですので、例えば値が1の場合のみ成功パターンとして扱い、他の値の場合はエラーメッセージを出したいとすれば、1 => success(), _ => println!("Error!") のようになるでしょう。

またmatchは式なので返り値を取得できます。この例ではそれぞれのパターンからの返り値Stringをinput_byteに再び入れています。

#![allow(unused)]
use std::io::Error;

fn main() {
    let input_byte: std::result::Result<&str, Error> = Ok("This is ok");
    let input_byte: String = match input_byte {
        Ok(value) => value.to_string(),
        Err(value) => value.to_string(),
        _ => "This is Unexpected".to_string(),
    };
    println!("{}", input_byte);

    let input_byte: std::result::Result<&str, Error> = Err(Error::other("This is Error"));
    let input_byte: String = match input_byte {
        Ok(value) => value.to_string(),
        Err(value) => value.to_string(),
        _ => "This is Unexpected".to_string(),
    };
    println!("{}", input_byte);

    println!("End of main()");
}Code language: PHP (php)

playground

?演算子

Okの処理だけをmatch式で記述したい場合、全てのパターンを網羅しないといけない制約はコードが冗長になってしまいます。

Rustでは?演算子が用意されており、Okの値を取り出すことができるショートカットが用意されています。Err処理は移譲されます。そのため、Resultを返すメソッドや関数内でしか?演算子は使えません。

#![allow(unused)]
use std::io::Error;

fn main() -> Result<(), Error> {
    let input_byte: std::result::Result<&str, Error> = Ok("This is ok");
    let input_byte = input_byte?;
    println!("{}", input_byte);

    let input_byte: std::result::Result<&str, Error> = Err(Error::other("This is Error"));
    let input_byte = input_byte?;
    println!("{}", input_byte);

    println!("End of main()");

    Ok(())
}
Code language: JavaScript (javascript)

playground

if let 式

より簡潔に記述したい場合は、if let 式がよいでしょう。この例では、Okの値をvalueにキャプチャできた場合、成功パターンを記述しています。Errを処理したいケースも同様に記述可能です。

この式は?演算子よりも制約がなく使いやすいです。

この例では、match式と同様に返り値をinput_byteに入れようとしていますが、else文を書かない場合、返り値として()が返されてしまいStringとマッチしません。そのため、Stringを返すようにelseを書いています。

#![allow(unused)]
use std::io::Error;

fn main() {
    let input_byte: std::result::Result<&str, Error> = Ok("This is ok");
    let input_byte: String = if let Ok(value) = input_byte {
        value.to_string()
    } else {
        "This is Unexpected".to_string()
    };
    println!("{}", input_byte);

    let input_byte: std::result::Result<&str, Error> = Err(Error::other("This is Error"));
    let input_byte: String = if let Err(value) = input_byte {
        value.to_string()
    } else {
        "This is Unexpected".to_string()
    };
    println!("{}", input_byte);

    println!("End of main()");
}
Code language: JavaScript (javascript)

playground

マクロ

println!()に付いても説明していませんでした。

単語の最後に!マークがつく文はマクロと呼ばれるものです。また#[derive]などもマクロの一種です。

println!マクロの使い方は今まで見てきた通り関数と同じです。大きな違いはマクロは引数を可変にすることができるということでしょうか。

ということでprintln!で16進数を表示するように改良してみましょう。

といっても難しくはありません。Hex: {0:#X} を足すだけです。

println!マクロの基本的な使い方はプレースホルダーと同じ数の変数を並べる使い方ですが、

プレースホルダーに引数の番号を指定すると同じ数でなくても出力することができます(この例ではコロンの左が引数 0番)。3

match式も追加したコードは下記のようになります。

// error_handling

// 標準ライブラリの読み込み
use std::io::{self, Read};
// crosstermの読み込み
use crossterm::terminal::disable_raw_mode;
use crossterm::terminal::enable_raw_mode;

// main 関数
fn main() {
    // raw モードに移行
    enable_raw_mode().unwrap();

    // STDINからbyte列を読み込み
    for input_byte in io::stdin().bytes() {
        // match式でResultを処理
        match input_byte {
            // 正常ケース
            Ok(input_byte) => {
                // 読み込んだbyteをcharに変換して束縛
                let output_char = input_byte as char;

                // 読み込んだ値を出力
                if output_char.is_control() {
                    println!("Hex: {0:#X} Binary: {0:08b} ASCII: {0:#03} \r", input_byte);
                } else {
                    println!(
                        "Hex: {0:#X} Binary: {0:08b} ASCII: {0:#03} Character: {1:#?}\r",
                        input_byte, output_char,
                    );

                    // qが入力されたらbreak
                    if output_char == 'q' {
                        // raw モードを解除
                        disable_raw_mode().unwrap();
                        break;
                    }
                }
            }
            // エラーの場合
            Err(err) => println!("Error: {}", err),
        }
    }
}
Code language: PHP (php)

  1. シャドーイングを駆使しすぎてわかりづらいコードになっています。良くない例です。 ↩︎
  2. アンダーバーの評価をOkやErrの評価の前に持ってくるとアンダーバーの評価だけになりますので注意。 ↩︎
  3. 書式についてはこちら↩︎

GitHubにコードをアップロードしています。0.1.0バージョン

コードのコメントに書かれているfirst_stepなどをcargoコマンドに渡すと実行できます。

# Example
$ cargo run --example first_stepCode language: Bash (bash)
管理人

Recent Posts

情報セキュリティマネジメント試験取得への道

スキルアップを図るべく情報セキ…

2か月 ago

ファイナンシャルプランナー3級試験取得への道

スキルアップを図るべくファイナ…

2か月 ago

[rust] New Type Patternを使ってみる

DDDの考えを取り入れることで…

5か月 ago

RustでDDDの要素を取り入れてみる

前回SOLID原則というものを…

5か月 ago

期間限定!書籍無料キャンペーン2025

「mdBookではじめるKin…

5か月 ago