隱含轉換

比起先前提到的下列程式碼,? 的有效擴展稍微更複雜一點:

expression?

運作方式與以下程式碼相同:

match expression {
    Ok(value) => value,
    Err(err)  => return Err(From::from(err)),
}

The From::from call here means we attempt to convert the error type to the type returned by the function. This makes it easy to encapsulate errors into higher-level errors.

範例

use std::error::Error;
use std::fmt::{self, Display, Formatter};
use std::fs::File;
use std::io::{self, Read};

#[derive(Debug)]
enum ReadUsernameError {
    IoError(io::Error),
    EmptyUsername(String),
}

impl Error for ReadUsernameError {}

impl Display for ReadUsernameError {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        match self {
            Self::IoError(e) => write!(f, "IO error: {e}"),
            Self::EmptyUsername(path) => write!(f, "Found no username in {path}"),
        }
    }
}

impl From<io::Error> for ReadUsernameError {
    fn from(err: io::Error) -> Self {
        Self::IoError(err)
    }
}

fn read_username(path: &str) -> Result<String, ReadUsernameError> {
    let mut username = String::with_capacity(100);
    File::open(path)?.read_to_string(&mut username)?;
    if username.is_empty() {
        return Err(ReadUsernameError::EmptyUsername(String::from(path)));
    }
    Ok(username)
}

fn main() {
    //fs::write("config.dat", "").unwrap();
    let username = read_username("config.dat");
    println!("username or error: {username:?}");
}
This slide should take about 5 minutes.

? 運算子必須傳回與函式傳回類型相容的值。如果是 Result,表示錯誤類型必須相容。如果是傳回 Result<T, ErrorOuter> 的函式,當 ErrorOuterErrorInner 的型別相同、或者 ErrorOuter 實作 From<ErrorInner> 時,就只能在 Result<U, ErrorInner> 型別的值上使用 ?

From 實作的常見的替代方案是 Result::map_err,特別是當轉換只在單一位置發生時更是如此。

Option 並沒有相容性規定。如果函式會傳回 Option<T>,可以在 Option<U> 上將 ? 運算子用於任意的 TU 型別。

傳回 Result 的函式無法在 Option 上使用 ?,反之亦然。不過,Option::ok_or 會將 Option 轉換為 Result,而 Result::ok 則將 Result 轉換為 Option