TL;DR
OptionとResultは、明示的なmatchで分岐するより、?演算子を使える型に変換して扱う。- 変換に参照が絡むときは
as_refを使う。 OptionよりResultを使う。
内容
https://effective-rust.com/transform.html
目的別の書き方
None や Err を無視したいなら、if let を使う。
if let Some(i) = &s.field {
println!("field is {i}");
}None や Err のときにパニックさせたいなら、unwrap か expect を使う。expect には失敗時のメッセージを指定できる。ただし、エラー処理は呼び出し側に判断を任せるのが正しいことが多い。特にライブラリは、作者が予想できないさまざまな環境で使われる。利用者の負担を減らすためにも、Result と Option を返すほうがよい。
エラーを呼び出し元に伝えるなら、? 演算子を使う。? は Err にマッチし、必要ならエラー型を変換して return Err(...) するシンタックスシュガーである。? が呼ぶメソッドは #[inline] が付いているので、手で書いたコードと同じになり、性能への影響はほとんどない。
pub fn find_user(username: &str) -> Result<UserId, std::io::Error> {
let f = std::fs::File::open("/etc/passwd")?;
// ...
}エラーを手動で別の型に変換するなら、map_err を使う。From トレイトを実装しておけば、map_err を呼ばなくても ? が自動で変換する(項目4)。
pub fn find_user(username: &str) -> Result<UserId, String> {
let f = std::fs::File::open("/etc/passwd")
.map_err(|e| format!("Failed to open password file: {:?}", e))?;
// ...
}変換メソッド
この種の変換は一般化でき、標準ライブラリには Option と Result を相互に、また中身の型を変換するメソッドが多く用意されている。対応関係は原書の同じページに図でまとめられており、panic! しうるメソッドには印が付いている。
よく使うものに、&Option<T> を Option<&T> に変換する as_ref がある。
struct InputData {
payload: Option<Vec<u8>>,
}
impl InputData {
pub fn encrypted(&self) -> Vec<u8> {
encrypt(self.payload.as_ref().unwrap_or(&vec![]))
}
}Result と Option のどちらを使うか
次の 2 つの理由から Result を使ったほうがよい。
- エラーが起きたとき、
Errに役立つ情報を持たせられる Resultには#[must_use]属性が付いているので、返されたResultを無視するとコンパイラが警告する