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でさまざまな更新が進んでいるようで、今後はもっと使いやすくなることが期待されます。
より詳細を知りたい方や余力がある方はぜひ公式ドキュメントも確認してみてください。



