?
の他の活用法
以前の例ではparse
の呼び出しに対するその場での対応として、エラーをライブラリのエラーからboxされたエラーへとmap
していました。
.and_then(|s| s.parse::<i32>())
.map_err(|e| e.into())
簡単でよくあるオペレーションのため、可能なら省略してしまえると便利だったでしょう。でも残念、and_then
が十分にフレキシブルでないため、それはできません。ただその代わり、?
なら使えます。
?
の挙動は、unwrap
またはreturn Err(err)
として説明されていました。これはほぼ正解で、本当はunwrap
、もしくはreturn Err(From::from(err))
という意味があります。From::from
は異なる型の間での変換ユーティリティであることから、エラーがリターン型に変換可能な場合に?
を使うことで、その変換を自動的に行ってくれます。
前の例を?
を使ったものに書き換えてみましょう。その結果、From::from
がエラー型に実装されている時map_err
は消えてなくなります。
use std::error; use std::fmt; // エイリアスを`Box<error::Error>`に変更します。 type Result<T> = std::result::Result<T, Box<dyn error::Error>>; #[derive(Debug)] struct EmptyVec; impl fmt::Display for EmptyVec { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "invalid first item to double") } } impl error::Error for EmptyVec {} // 前と同じ構造ですが、`Results`と`Option`を繋げていく代わりに、 // `?`で内部の値をその場で取得します。 fn double_first(vec: Vec<&str>) -> Result<i32> { let first = vec.first().ok_or(EmptyVec)?; let parsed = first.parse::<i32>()?; Ok(2 * parsed) } fn print(result: Result<i32>) { match result { Ok(n) => println!("The first doubled is {}", n), Err(e) => println!("Error: {}", e), } } fn main() { let numbers = vec!["42", "93", "18"]; let empty = vec![]; let strings = vec!["tofu", "93", "18"]; print(double_first(numbers)); print(double_first(empty)); print(double_first(strings)); }
これでかなり綺麗になりました。元のpanic
と比べ、リターン型がResult
であることを除けば、unwrap
の呼び出しを?
で置き換えたものに非常に似ています。結果、そのResult
は上のレベルで分解されなければなりません。