Optionに続きResultの紹介です。
Optionと同様にRustコーディングの肝といえる存在なので大事ですね。
前回同様、自身の勉強のアウトプット・整理と同時に、同じようにRustを勉強している方や「公式ドキュメントはちょっと、、」というような方が気楽に見れるようにサンプルコードをメインに簡単にまとめてみました。
- Result型の基本
- Result型の便利なメソッドとその使い方
基本
Result<T,E>
は次のいずれかの値を示す型です。
Ok(T)
:成功もしくは正常な状態Err(E)
:失敗もしくは異常な状態
Result<T,E>自体の実装は下記のようにとてもシンプルです。
enum Result<T, E> {
Ok(T),
Err(E),
}
Option
もそうでしたが、ただの列挙型(Enum)です。
正常な状態を示したい場合はOk(T)
、エラーを示したい場合はErr(E)
というようになります。
使い方は以下のようになります。
fn sample() {
let ok: Result<i32, &str> = Ok(200);
println!("{:?}", ok);
// -> Ok(200)
let error: Result<i32, &str> = Err("failed");
println!("{:?}", error);
// -> Err("failed")
}
便利なメソッド
公式ドキュメントからResultで特に使いそうなメソッドをピックアップしてみました。
サンプルコードとともに注意点も簡単に記載しています。
unwrap
Ok(T)
の中身を取得する。Err
の場合はpanic
を引き起こす。
所有権の移動が発生するので同じものに対して複数回は実行できない。
fn sample() {
let ok: Result<i32, String> = Ok(200);
println!("{:?}", ok.unwrap()); // -> 200
println!("{:?}", ok.unwrap()); // 上で所有権が移動済なのでコンパイルエラーとなる
let error: Result<i32, String> = Err("failed".to_string());
println!("{:?}", error.unwrap()); // Errの場合はpanicとなる
}
公式ドキュメントでは基本的に使用は推奨されておらず、Err
ケースを処理できるような方法が望ましいとされている。(unwrap_orなどを使うなど)
unwrap_or
unwrap
の派生形。
Err
に対して実行した場合は引数に指定した値が返却される。
fn sample() {
let ok: Result<i32, String> = Ok(200);
println!("{:?}", ok.unwrap_or(-1));
// -> 200
println!("{:?}", ok.unwrap_or(-1));
// 上で所有権が移動済なのでコンパイルエラーとなる
let error: Result<i32, String> = Err("failed".to_string());
println!("{:?}", error.unwrap_or(-1));
// -> -1
}
unwrap_or_else
unwrap_or
と同様だが、引数には関数を渡す。
Err
の場合に引数に渡した関数が実行されその結果が返却される。
fn sample() {
let ok: Result<i32, String> = Ok(200);
println!("{:?}", ok.unwrap_or_else(|str| -1));
// -> 200
println!("{:?}", ok.unwrap_or_else(|str| -1));
// 上で所有権が移動済なのでコンパイルエラーとなる
let error: Result<i32, String> = Err("failed".to_string());
println!("{:?}", error.unwrap_or_else(|str| -1));
// -> -1
}
unwrap_or_default
unwrap_or
と同様だが、引数指定なしで使用可能。
Err
の場合にResult<T,E>
におけるT
型のデフォルト値を返すことができる。
fn sample() {
let ok: Result<i32, String> = Ok(200);
println!("{:?}", ok.unwrap_or_default());
// -> 200
println!("{:?}", ok.unwrap_or_default());
// 上で所有権が移動済なのでコンパイルエラーとなる
let error: Result<i32, String> = Err("failed".to_string());
println!("{:?}", error.unwrap_or_default());
// -> 0
}
expect
Ok(T)
の中身を取得する。Err
の場合は引数に指定した文字列でpanic
を発生。
所有権の移動が発生するので同じ物に対して複数回は行えない。
fn sample() {
let ok: Result<i32, String> = Ok(200);
println!("{:?}", ok.expect("no error"));
// -> 200
println!("{:?}", ok.expect("compile error"));
// 上で所有権が移動済なのでコンパイルエラーとなる
let error: Result<i32, String> = Err("failed".to_string());
println!("{:?}", error.expect("panic!!"));
// -> Errの場合はpanicとなる
}
as_ref
Result<T,E>
をResult<&T,&E>
にして参照する。
unwrap
やmap
などのメソッドはResult<T,E>
の中身を消費(所有権を移動)するメソッドだが、as_ref
を使用して参照を挟むことで消費しないようにすることができる。
fn sample() {
let ok: Result<i32, String> = Ok(200);
println!("{:?}", ok.unwrap()); // unwrapで所有権が移動する
println!("{:?}", ok.unwrap()); // 上の行で所有権が移動済なのでコンパイルエラーとなる
let ok2: Result<i32, String> = Ok(200);
println!("{:?}", ok2.as_ref().unwrap()); // as_refをはさむことで所有権の移動が発生しない
println!("{:?}", ok2.unwrap()); // この時点ではまだ所有権は移動していないのでコンパイルエラーとならない
println!("{:?}", ok2.as_ref().unwrap()); // 上の行で所有権が移動済なので参照も作れない(コンパイルエラー)
}
map
Result<T,E>
をResult<U,E>
にする。
引数に渡した関数で変換・整形処理を行い、その結果としてResult<U,E>
を返却する。
Err
に対して実行した場合、引数に渡した関数は実行されないことに注意。
fn sample() {
let ok: Result<i32, String> = Ok(200);
println!("{:?}", ok.map(|s| s * 2));
// -> Ok(400)
let error: Result<i32, String> = Err("failed".to_string());
println!("{:?}", error.map(|s| s * 2));
// -> Err("failed") ※mapに渡した関数は実行されない
}
map_or
map
にデフォルト値がついたもの。
戻り値がResult
型ではなく、中身のT
型となることに注意。
map
と同様に、Err
の場合は引数に渡した関数は実行されないことに注意。
fn sample() {
let ok: Result<i32, String> = Ok(200);
println!("{:?}", ok.map_or(999, |s| s * 2));
// -> 400
let error: Result<i32, String> = Err("failed".to_string());
println!("{:?}", error.map_or(999, |s| s * 2));
// -> 999 ※mapに渡した関数は実行されない
}
map_or_else
map_or
のデフォルト値指定部分が関数になったもの。
fn sample() {
let ok: Result<usize, String> = Ok(200);
println!("{:?}", ok.map_or_else(|error| error.len(), |s| s * 2));
// -> 400
let error: Result<usize, String> = Err("failed".to_string());
println!("{:?}", error.map_or_else(|error| error.len(), |s| s * 2));
// -> 6 ※mapに渡した関数は実行されない
}
ok
Result<T,E>
をOption<T>
に変換する。
Ok(T)
の場合はSome(T)
に、Err(E)
の場合はNone
にそれぞれ変換される。
fn sample() {
let ok: Result<usize, String> = Ok(200);
println!("{:?}", ok.ok());
// -> Some(200)
let error: Result<usize, String> = Err("failed".to_string());
println!("{:?}", error.ok());
// -> None
}
err
Result<T,E>
をOption<T>
に変換する。
Ok(T)
の場合はNone
に、Err(E)
の場合はSome(E)
にそれぞれ変換される。
fn sample() {
let ok: Result<usize, String> = Ok(200);
println!("{:?}", ok.err());
// -> None
let error: Result<usize, String> = Err("failed".to_string());
println!("{:?}", error.err());
// -> Some("failed")
}
and / or
and
,or
という名前ですが、真理値表のように考えると混乱するかも。
ドキュメントにあるように以下のように動作することに注意。
and | Ok(T) の場合は引数に渡した値を、Err(E) の場合はErr(E) 自身を返す |
or | Ok(T) の場合はOk(T) 自身を、Err(E) の場合は引数に渡した値を返す |
fn sample() {
let ok: Result<i32, &str> = Ok(200);
let another_ok: Result<i32, &str> = Ok(999);
let error: Result<i32, &str> = Err("failed");
let another_error: Result<i32, &str> = Err("another failed");
// and
println!("{:?}", ok.and(another_ok)); // -> Ok(999)
println!("{:?}", ok.and(error)); // -> Err("failed")
println!("{:?}", error.and(ok)); // -> Err("failed")
println!("{:?}", error.and(another_error)); // -> Err("failed")
// or
println!("{:?}", ok.or(another_ok)); // -> Ok(200)
println!("{:?}", ok.or(error)); // -> Ok(200)
println!("{:?}", error.or(ok)); // -> Ok(200)
println!("{:?}", error.or(another_error)); // -> Err("another failed")
}
and_then / or_else
and
, or
の引数が関数になったもの。
fn sample() {
let ok: Result<i32, &str> = Ok(200);
let another_ok: Result<i32, &str> = Ok(999);
let error: Result<i32, &str> = Err("failed");
let another_error: Result<i32, &str> = Err("another failed");
// and
println!("{:?}", ok.and_then(|i| another_ok)); // -> Ok(200)
println!("{:?}", ok.and_then(|i| error)); // -> Err("failed")
println!("{:?}", error.and_then(|i| ok)); // -> Err("failed")
println!("{:?}", error.and_then(|i| another_error)); // -> Err("failed")
// or
println!("{:?}", ok.or_else(|s| another_ok)); // -> Ok(200)
println!("{:?}", ok.or_else(|s| error)); // -> Ok(200)
println!("{:?}", error.or_else(|s| ok)); // -> Ok(200)
println!("{:?}", error.or_else(|s| another_error)); // -> Err("failed")
}
最後に
2つの型を内包している分、Option
よりもResult
のほうが難しく感じます。
ですが、エラーを値としてコントロールできるというのは安心感があります。
Result
関連のメソッドはnightly channelでさまざまな更新が進んでいるようで、今後はもっと使いやすくなることが期待されます。
より詳細を知りたい方や余力がある方はぜひ公式ドキュメントも確認してみてください。