?の他の活用法

以前の例では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は上のレベルで分解されなければなりません。

参照

From::from, ?