63
34

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Rustのエラー処理

Last updated at Posted at 2018-12-04

この記事はWanoアドベントカレンダーの5日目の記事です。

2020/07現在の状況について

エラー処理周りは状況が少し変わりました。
具体的には標準ライブラリ(std)のstd::error::Errorにbacktraceに関する機能追加が行われ、failure::Failが不要になりました。
引き続きfailureを使っても良いのですが、最近はfailure::Error相当の物を使う為にanyhowを、std::error::Errorを簡単にimplする為にthiserrorを使うのがおすすめです。

TL;DR

  • Result型の値の項の後ろに?を付けると例外っぽく振舞う
  • failureを使う

エラー処理の方法

Rustには例外がありません。
Go言語と同じように戻り値でエラーを返します(同様にpanicもあります)。

Rustでは『処理結果もしくはエラー内容』を表す型としてResult型というのがあります。
Result型はenumとして次の様に定義されています。

pub enum Result<T, E> {
    Ok(T),
    Err(E),
}

Ok(T)が正常に処理できた結果、Err(E)が何らかのエラーが発生したことを表します。

例えば、バイト列からStringを作るString::from_utf8メソッドは戻り値の型がResult<String, FromUtf8Error>です。
これはStringを作る処理が成功したらOk(String)が返り、不正なutf8バイト列を与える等するとErr(FromUtf8Error)が返る事を表しています。

呼び出し側はこのResultmatchによるパターンマッチか、Result型のメソッドのmap等を繋げるメソッドチェーンで処理します。

パターンマッチで処理する場合:

use std::fs;

fn main() {
    match fs::read_to_string("/file_does_not_exist") {
        Ok(s) => println!("Content => {}", s),
        Err(err) => eprintln!("IO Error => {}", err),
    }
}

メソッドチェーンで処理する場合:

use std::fs;

fn main() {
    fs::read_to_string("/file_does_not_exist")
        .map(|s| println!("Content => {}", s))
        .unwrap_or_else(|err| eprintln!("IO Error => {}", err));
}

どちらを使うかは書いている処理に合わせて選択します。

?オペレーター

エラー処理をする時に全てのエラーをその場で処理する事はそんなになく、呼び出し元にエラー処理を委ねる事が多いです。

これを普通に書くと下記の様にいちいちmatchしてreturnしなくてはならず、とても面倒です。

use std::fs;
use std::io;

fn load_2files_and_print() -> Result<(), io::Error> {
    match fs::read_to_string("/file_does_not_exist_1") {
        Ok(s) => println!("Content1 => {}", s),
        Err(err) => return Err(err),
    }

    match fs::read_to_string("/file_does_not_exist_2") {
        Ok(s) => println!("Content2 => {}", s),
        Err(err) => return Err(err),
    }

    Ok(())
}

fn main() {
    load_2files_and_print()
        .unwrap_or_else(|err| eprintln!("IO Error => {}", err));
}

そこでこの手間を減らす為に?オペレーターという機能があります。
これはResultを返す式の最後に?を書くと、Ok(T)ならばTの値を返し、Err(E)ならばErr(E)をreturnしてくれるという機能です。
また単にErr(E)をreturnするだけではなく、関数の戻り値型がResult<T, E2>の時にFrom::fromによる型変換も自動的に行ってくれます。

これを使うと上記のコードは次の様になります。

use std::fs;
use std::io;

fn load_2files_and_print() -> Result<(), io::Error> {
    println!("Content1 => {}", fs::read_to_string("/file_does_not_exist_1")?);
    println!("Content2 => {}", fs::read_to_string("/file_does_not_exist_2")?);
    Ok(())
}

fn main() {
    load_2files_and_print()
        .unwrap_or_else(|err| eprintln!("IO Error => {}", err));
}

圧倒的にシンプルになりました。

独自のエラー型を返す

※ 2020/07/24追記: 現在ではthiserrorを使ってstd::error::Errorを実装するのが簡単でおすすめです。

ライブラリ等を書いていると独自のエラー型を返したくなる事があります。
そのような時はfailure crateを使ってエラー型を定義すると簡単に独自エラー型を用意できます。

failureはFail traitを提供しており、独自エラー型でこれを実装します。
Failは自動実装の為のマクロが提供されているので、#[derive(Fail)]とするだけでほとんどの実装を行ってくれます。
ほとんどの実装というのはDisplay traitの実装が実は必須なのですが、これを自動では行ってくれないのです。
しかし簡単にDisplay traitの実装を用意できる機能が提供されています。
下記のコード中の#[fail(display = "...", ...)]の様に記述する事で後はfailure側で自動的にDisplay traitを実装を行ってくれます。

#[macro_use]
extern crate failure;

use std::fs;
use std::io;

#[derive(Debug, Fail)]
enum MyError {
    // これでDisplayの実装を用意してくれる。
    // この構文の記法はprintln!マクロ等とほぼ同じで、
    // フォーマット文字列の後にはフィールド名を書きます。
    #[fail(display = "An IO error occured: {}", 0)]
    IoError(io::Error),
}

// Fromは自分で実装...
impl From<io::Error> for MyError {
    fn from(err: io::Error) -> MyError {
        MyError::IoError(err)
    }
}

// MyError型をエラー型として使えるようになった!
fn load_and_print() -> Result<(), MyError> {
    println!("{}", fs::read_to_string("/file_does_not_exist")?);
    Ok(())
}

fn main() {
    load_and_print().unwrap_or_else(|err| eprintln!("{}", err));
}

これをビルドして実行すると次の様な出力になります。

An IO error occured: No such file or directory (os error 2)

独自のエラー型を?オペレーターで返してエラーメッセージの表示まで出来ました。

failureのより詳しい使い方等は下記のドキュメントを参照してください。
https://boats.gitlab.io/failure/intro.html

63
34
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
63
34

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?