Try変換
?
を実際に展開すると、前述のコードよりも少し複雑なコードになります。
expression?
上のコードは、以下と同じように動作します。
match expression {
Ok(value) => value,
Err(err) => return Err(From::from(err)),
}
ここでの From::from
呼び出しは、エラー型を関数が返す型に変換しようとしていることを意味します。これにより、エラーを上位レベルのエラーに簡単にカプセル化できます。
例
use std::error::Error; use std::io::Read; use std::{fmt, fs, io}; #[derive(Debug)] enum ReadUsernameError { IoError(io::Error), EmptyUsername(String), } impl Error for ReadUsernameError {} impl fmt::Display for ReadUsernameError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Self::IoError(e) => write!(f, "I/O 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); fs::File::open(path)?.read_to_string(&mut username)?; if username.is_empty() { return Err(ReadUsernameError::EmptyUsername(String::from(path))); } Ok(username) } fn main() { //std::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>
を返す関数は、ErrorOuter
と ErrorInner
が同じ型であるか、ErrorOuter
が From<ErrorInner>
を実装している場合にのみ、型 Result<U, ErrorInner>
の値に ?
を使用できます。
特に変換が 1 か所でのみ発生する場合は、From
を実装する代わりに Result::map_err を使用するのが一般的です。
Option
には互換性の要件はありません。Option<T>
を返す関数は、任意のT型とU型に対して、?演算子をOption
Result
を返す関数では Option
に ?
を使用できません。その逆も同様です。ただし、Option::ok_or
は Option
を Result
に変換でき、Result::ok
は Result
を Option
に変換できます。