###### tags: `Rust` `compile` `条件付き` `エラーハンドリング`
# Better Rust 2
# #[cfg()] 条件付きコンパイルアトリビュート
フラグに合わせてコンパイルする
# エラーハンドリング
プログラムを書く上でエラーの対処は重要
Rustでは、エラーの対処が冗長で面倒なものにならないような仕組みが
ある。
sum type (直和型) と コンビネータ から、エラーハンドルを改善する動機を学ぶ。
## エラー解決の基礎
ある処理が成功したか失敗したかを場合分けに基づいて判断する。
エラーハンドリングを使いやすくするには、合成可能(コンパイルできる)なコードを保ったまま
明示的な場合分け回数を以下に減らすかが重要。
コード合成可能性 = 想定外のときpanic!する可能性を減らす。
panicすると、多くの場合はプログラムがクラッシュする。
## unwrap() メソッド
Option\<T> と Result\<T> に実装されているメソッド。
.unwrap() の意味は、「値が取得できているならその値を返し、ないならpanic!して」
- Option\<T> : 値が不在の可能性。Some(T) と None の Variant を持つ
- Result\<T, E> : 実行結果。Some(T) と Err(E) のVariantを持つ
どちらのenum型も、正しい値が取得できないかもしれないという可能性を持つ
## Optionを返す関数のmatch
fn() -> Option\<T> 型の関数のエラーハンドリング
```rust=
fn main() {
let file_name = "foobar.rs";
match find(file_name, '.') {
None => println!("No file extension found."),
Some(i) => println!("File extension: {}", &file_name[i+1..]),
}
}
```
match キーワードを使って、 None と Some(i) のパターンマッチングで値を取得して使用する。
## unwrap() を使うときの場合分け
実際のところ、Option を処理するには match による場合分けを行うしかない。
unwrap() 内で行っていることはまさにそれであり、
- 値が取得できれば値を返す
- なければpanic!する
ということを行っているのである。
定義はかんたんなので、自分でOption型の unwarp() メソッドを定義し直すことで
Noneの場合の処理をカスタマイズすることができる。
```rust=
enum Option<T> {
None,
Some(T),
}
impl<T> Option<T> {
fn unwrap(self) -> T {
match self {
Option::Some(val) => val,
Option::None =>
panic!("called `Option::unwrap()` on a `None` value"),
}
}
}
```
デフォルトの panic! が実装されているunwrap() は、合成可能ではない(プログラムがクラッシュする)
陶器店の中の雄牛
## Option\<T> 型の値を合成する
関数などの戻り値にするとき
- Some(T) を返す
- None を返す
これだけでいい
### 例.指定したファイル名の拡張子を取り出す関数
プログラムからファイルを読み込むとき、拡張子を取り出すのはままあること
なので、標準ライブラリで extension() として関数化されている
```rust=
fn extension_explicit(file_name: &str) -> Option<&str> {
match find(file_name, '.') {
None => None,
Some(i) => Some(&file_name[i+1..]),
}
}
```
ファイル名内の最初の . 以降 の文字列のスライスをSome()でラップして返す
拡張子がない可能性を考慮してNoneも返す
## コンビネータ
Option型と組み合わせて使いましょうね、という
Option型構造体のメソッドとして実装されている関数
### option.map(arg, fn)
mapとは : map関数。値と関数を引数にして、関数を適用した値を返す。
イテレータなどでよく使う
```rust=
fn map<F, T, A>(option: Option<T>, f: F) -> Option<A> where F: FnOnce(T) -> A {
match option {
None => None,
Some(value) => Some(f(value)),
}
}
```
これはOption\<T>型のメソッドとして定義されている。
しかし、戻り値はSome()でラップされ直すので使いづらい。
## option.and_then(fn)
option型の中身を場合分けして、
値があるときは引数の関数を適用した値を返す。
option.map() のSome() で再ラップしないバージョン。
例.ファイルパスからファイルを見つけて、ファイル名から拡張子を抜き出す。
```rust=
fn file_path_ext(file_path: &str) -> Option<&str> {
file_name(file_path).and_then(extension)
}
```
# Result\<T, E> 型
標準ライブラリで定義される。
Optionの豪華版で、Err\<E>のように失敗時の返り値をつけられる。
値の取得に失敗する場合などに、なぜ失敗したのかを説明するために用いる。
Option型は、Result型を特殊化したもの。
Err(E) が () 空タプルになったもの
```rust=
type Option<T> = Result<T, ()>;
```
Variantは以下の2つ
- Ok(T)
- Err(E)
Option型と同様に、 unwrap()が実装されている
## 文字列を整数値にパース(変換)
Rustのstdを使うと、かんたんにstrをintに変換できる。
しかし、
- 空文字
- 整数値以外が含まれてる
といった理由でパースが失敗する可能性もある
Result\<T, E>を使うことでエラーハンドリングができる
```rust=
impl str {
fn parse<F: FromStr>(&self) -> Result<F, F::Err>;
}
```
&str 型は、 .parse() メソッドを実装している。
パースできる型は、ジェネリックにより FromStr トレイトを実装している型のみ
FromStrトレイトの実装を見ると、関連型にParseIntError がある。
このエラーが発生する可能性のある関数では、Resultのジェネリックパラメータに
これを使おう
```rust=
use std::num::ParseIntError;
fn double_number(number_str: &str) -> Result<i32, ParseIntError> {
number_str.parse::<i32>().map(|n| 2 * n)
}
fn main() {
match double_number("10") {
Ok(n) => assert_eq!(n, 20),
Err(err) => println!("Error: {:?}", err),
}
}
```
Result型もOption型と同様に、 map(), and_then() コンビネータメソッドを実装している。
Option型との違いとして、Err(E) のみに影響をあたえる .map_err() や or_else() がある。
## Result型のイディオム
### ネームスペースにおいて、Result型が返すErrの種類を固定する
```rust=
use std::num::ParseIntError;
use std::result;
type Result<T> = result::Result<T, ParseIntError>;
fn double_number(number_str: &str) -> Result<i32> {
unimplemented!();
}
```
type キーワードを用いた型エイリアスにより、 パラメータ1つの Result\<T>を定義
ParseIntErrorが大量に使われる場合など、タイピング量をへらすことができる
## OptionをResultに変換
.ok_or(option, err) コンビネータ
- Some(T) のときは Ok(T) を返す
- None のときは Err(err) にして返す
```rust=
fn ok_or<T, E>(option: Option<T>, err: E) -> Result<T, E> {
match option {
Some(val) => Ok(val),
None => Err(err),
}
}
```
# stdライブラリによるエラー処理
stdで定義されている
エラーハンドリングに欠かせないトレイト2種類
- Error トレイト
- From トレイト
## Errorトレイト
Debug トレイト と Display トレイトを継承している。
- Display .fmt() : println!マクロで"{}"のreplacementに表示する文字列を自動実装
- Debug .fmt() : 上の"{:?}"バージョン
上記に加えて、
- description() : エラー原因メッセージを返す
- cause() : 一段下のエラーを返す。(あれば)
cause() メソッドが Errorを入れ子で返すので、複数エラーに起因する場合でもエラーを追える
```rust=
use std::fmt::{Debug, Display};
trait Error: Debug + Display {
/// エラーの簡単な説明
fn description(&self) -> &str;
/// このエラーの一段下のレベルの原因(もしあれば)
fn cause(&self) -> Option<&Error> { None }
}
```
## From トレイト
std::convert::From
ある値を別の値に変換するための from(T)->Self を実装している
```rust=
trait From<T> {
fn from(T) -> Self;
}
```
驚くほど単純
使用例
```rust=
let string: String = From::from("foo");
let bytes: Vec<u8> = From::from("foo");
let cow: ::std::borrow::Cow<str> = From::from("foo");
```
## try! マクロ
本当のtry!マクロ
```rust=
macro_rules! try {
($e:expr) => (match $e {
Ok(val) => val,
Err(err) => return Err(::std::convert::From::from(err)),
});
}
```
try!(expr) の expr(式) を評価して、Ok() なら val, Err(err) なら Err(err)
エラーが起きたときだけ Err() でラップして返す
さらに、Err(err) の内部の値は、自動的に型変換される(異なるエラー型であっても、同一のエラー型になる)
# エラーの章のまとめ
長いので、章の一番下のまとめを読むと良い