📌 panic!
基本的に復帰不能なエラーが発生したら,どうしようも出来ません.メッセージを表示してプログラムを終了する手っ取り早い方法が panic!
です
fn main() {
panic!("crash");
}
📌 Result 型
どこかでエラーが発生したとしても,すぐにプログラムを終了させるわけにはなかなかいきません.ある関数の内部でエラーが発生したら,それを呼び出し元に知らせる必要があります.そこで Result
型が使われます.
enum Result<T, E> {
Ok(T),
Err(E),
}
ここではファイル処理を考えてみます.既存のファイルを開いて処理をしたいとします.もし,ファイルが無ければ作成します.その場合,最初にファイルを開こうとしたときにエラーが発生し,そのエラーがファイルが無かったことを表していればファイルを新規に作成するようにします.ファイルを開く処理 File::open
は std::io::Result
型を返します.これは Result<T, Error>
型の別名です.
use std::{fs::File, io::ErrorKind};
fn main() {
let f = File::open("hello.txt");
let f = match f {
Ok(file) => file,
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
Option
型, Result
型ともに,値を取り出す unwrap
という関数があります.これは,もし値が None
, Err
のときに, panic!
を呼び出します.
pub fn unwrap(self) -> T
unwrap
が panic!
を呼び出すと,標準のエラーメッセージが表示されますが, unwrap
の代わりに expect
を呼ぶと,エラーメッセージに情報を追加することが出来ます.
pub fn expect(self, msg: &str) -> T
Option
型と Result
型の unwrap
は以下のように match
式を短くしたものです.
option.unwrap()
//
// ↓
//
match option {
Some(v) => v,
None => panic!(...),
}
result.unwrap()
//
// ↓
//
match result {
Ok(v) => v,
Err(e) => panic!(...),
}
📌 エラー伝搬
Option
型, Result
型を返す関数の中で,値が None
または Err
のときに,処理を中断して呼び出し元に値を返す仕組みが用意されています.それは ?
演算子を使います.
fn hoge() -> Option<i32> {
let a = Some(10);
let b = a?;
Some(b)
}
fn hoge() -> Option<i32> {
let a = None;
let b = a?; // return Option<i32>::None
Some(b)
}
📌 コンビネータ
Option
型, Result
型も コンビネータ です.このコンビネータがエラー処理のコードを大幅に削減してくれます.手続き型であれば,1つ1つの関数呼出しの結果がエラーか無効な値かを確認していきます.これだと,確認コードが大量に出来てしまいます.そこで,まずはエラー伝搬です.関数が Option
か Result
を返せば, ?
演算子を使ってチェーン方式で処理を記述することが出来ます.途中でエラーが発生すれば,処理を打ち切ってエラー伝搬されます.
let ret = open()?.read()?.replace()?.write()?.close()?;
コンビネータとは簡単に言うと, 高階関数 のことで,高階関数とは関数を引数に取る関数のことです.例えば,関数 open.read.replace.write.close
で考えてみると close(write(replace(read(open()))))
の関係に見えないでしょうか.ここで .
演算子がコンビネータであり,その役を担っているのが Option
型と Result
型と考えることが出来ます.
Option
型, Result
型にはコンビネータとしての便利なメソッドが多く用意されています.基本的なものとして, map
は値に関数を適用して,その結果をコンビネータに変換します.
pub fn map<U, F: FnOnce(T) -> U>(self, f: F) -> Option<U> // Option
pub fn map<U, F: FnOnce(T) -> U>(self, op: F) -> Result<U, E> // Result
and_then
は関数を適用して,その結果をそのまま返します.つまり, and_then
に渡す関数はコンビネータを返します.
pub fn and_then<U, F: FnOnce(T) -> Option<U>>(self, f: F) -> Option<U> // Option
pub fn and_then<U, F: FnOnce(T) -> Result<U, E>>(self, op: F) -> Result<U, E> // Result
コンビネータは型を合わせる必要があります. Option
のメソッドに渡す関数は.単純に T
型を返す関数や Option
を返す関数ならよいのですが, Result
を返す場合にはそのままでは利用できません.そこで, Option
と Result
には相互に変換するメソッドがいくつかあります.例えば, ok_or
は Option
から Result
に, ok
は Result
から Option
に変換します.これにより Option
や Result
を返す関数を1つのメソッドチェーン内に利用することが出来ます.
コンビネータは ?
演算子を使っていなければ,途中の処理で None
になったり, Err
になった場合,チェーンの最後の型で返ってきます.これによりエラー処理を書く場所が少なくなります.
構造体のメソッドのところでも少し触れましたが,コンビネータのメソッドの引数は self
が多いです.これは,メソッド呼び出しで, Option<u32>
が Option<f32>
になったり, Option<T>
が Result<T,E>
になったり型の変換を行っているからです.