📌 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>
型になったり型の変換を行っているからです.