到達点は、不在、回復可能な失敗、プログラムの前提違反を区別することです。前提は第06回です。
失敗も戻り値の型へ含める
OptionはSomeかNone、ResultはOkかErrです。検索で見つからないのは通常の不在、数値の読み取り失敗は理由のある失敗として表せます。unwrapは不在や失敗をpanicに変えるため、利用者の入力へ安易に使いません。
fn parse_minutes(input: &str) -> Result<u32, String> {
let value: u32 = input.trim().parse()
.map_err(|_| "0以上の整数を指定してください".to_owned())?;
if value > 1440 {
return Err("一日分は1440分以下です".to_owned());
}
Ok(value)
}
fn total(inputs: &[&str]) -> Result<u32, String> {
let mut result = 0_u32;
for input in inputs {
result = result.checked_add(parse_minutes(input)?)
.ok_or_else(|| "合計が範囲を超えました".to_owned())?;
}
Ok(result)
}
fn main() {
println!("{:?}", total(&["25", "15"]));
println!("{:?}", total(&["25", "abc"]));
let titles = ["基礎", "演習"];
println!("{}", titles.get(5).unwrap_or(&"見つかりません"));
}
src/main.rsの出力はOkの40、Errの説明、不在のメッセージです。疑問符は成功なら中身を取り出し、失敗なら現在の関数から早く返します。例外のように任意の場所へ飛ぶ構文としてではなく、戻り値の型と対応させて読みます。
エラー型も契約
小さなアプリではStringのエラーで始められます。呼び出し側が種類ごとに判断するライブラリなら、Parse、OutOfRange、Ioなどのenumで表す方が適切です。DisplayとErrorを実装すれば、説明と原因の連鎖も表現できます。
mapは成功値の変換、map_errは失敗値の変換、and_thenは次もResultを返す処理の接続に使います。覚える前にmatchで同じ処理を書いてみると意味を理解できます。panicは、予期する入力エラーの通常の伝達手段にはしません。
練習と解答
練習:不正な一行を飛ばして合計する仕様に変えるとき、疑問符をそのまま使えるでしょうか。
解答:その場所の疑問符は最初の失敗で関数を終了させます。行ごとにmatchして、成功値を足し、失敗した行番号と理由を別に集める設計へ変えます。読み飛ばしを利用者へ伝え、完全な集計と混同させないことが大切です。
公式資料
Resultによるエラー処理を参照できます。
Rust全20回の目次 | 前の回 | 次の回