Beginning Rustに以下のようなコードがありますが、
fn main() {
fn f1(x: i32) -> Result<i32, String> {
if x == 1 {
Err(format!("Err. 1"))
} else {
Ok(x)
}
}
fn f2(x: i32) -> Result<i32, String> {
if x == 2 {
Err(format!("Err. 2"))
} else {
Ok(x)
}
}
fn f3(x: i32) -> Result<i32, String> {
if x == 3 {
Err(format!("Err. 3"))
} else {
Ok(x)
}
}
fn f4(x: i32) -> Result<i32, String> {
if x == 4 {
Err(format!("Err. 4"))
} else {
Ok(x)
}
}
fn f(x: i32) -> Result<i32, String> {
let result1 = f1(x);
// -----------------------------------
// let result2 = f2(result1?); equivalent to
//
// if result1.is_err() { return result1; }
// let result2 = f2(result1.unwrap());
// -----------------------------------
// if result1.is_err() { return result1; }
let result2 = f2(result1?);
//if result2.is_err() { return result2; }
let result3 = f3(result2?);
//if result3.is_err() { return result3; }
f4(result3?)
}
// in short
// fn f(x: i32) -> Result<i32, String> {
// f4(f3(f2(f1(x)?)?)?)
// }
//
match f(2) {
Ok(y) => println!("{}", y),
Err(e) => println!("Error: {}", e),
}
match f(4) {
Ok(y) => println!("{}", y),
Err(e) => println!("Error: {}", e),
}
match f(5) {
Ok(y) => println!("{}", y),
Err(e) => println!("Error: {}", e),
}
}
元のソースでは、f(x: i32)は以下のようになっていますが、上のソースにコメント付きで変更しています。オリジナルのコードは説明のために冗長になってますが。
fn f(x: i32) -> Result<i32, String> {
let result1 = f1(x);
if result1.is_err() { return result1; }
let result2 = f2(result1.unwrap());
if result2.is_err() { return result2; }
let result3 = f3(result2.unwrap());
if result3.is_err() { return result3; }
f4(result3.unwrap())
}
比較すると、f2(result1?)の?はResult値の処理を行うsyntax sugarで以下のif文でのエラー処理(エラーの伝搬)とエラーがなけれが値を取り出すunwrap()処理を合わせて実行するのだと、
// -----------------------------------
// let result2 = f2(result1?); equivalent to
//
// if result1.is_err() { return result1; }
// let result2 = f2(result1.unwrap());
// -----------------------------------
テキスト中にも書かれていますが、
let ex = func();
match ex {
Ok(v) => v,
Err(_) => return ex,
}
と等価なのだと。実は?はResultのみならずOptionの戻り値処理でも使えると、当然OkはSomeでErrはNoneになりますが。
さらにmain()関数も普通の関数のようにResultを返すことができて、以下のようなコードが出てきます。
Okからば空のタプルを返却し、エラー(この場合は負の値を引数)ならばErrを返却するコードです。以下のコードではincremented(-3)でmain()が終了してしまうので、最後の2行は実行されませんが、戻り値としてのOk(())は無いとコンパイルできません。実行時にエラーが起こるかどうかはコンパイラは事前に知ることはできないので。
ここでも?演算子はErrはそのまま返していることがわかります。
/* It prints:
5
Error: "negative argument"
*/
fn main() -> Result<(), String> {
fn incremented(n: i32) -> Result<i32, String> {
if n < 0 { Err(format!("negative argument")) }
else { Ok(n + 1) }
}
println!("{} ", incremented(4)?);
println!("{} ", incremented(-3)?);
println!("{} ", incremented(7)?);
Ok(())
}
admin