Result型を扱う演算子?(@Rust)

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

カテゴリーRust