Bitterless Rust

Option と Result

Option: あるかないか

Rust には null がない.代わりに Option を使う:

enum Option<T> {
    Some(T),  // 値がある
    None,     // 値がない
}

<T> は「何の型でも入る」という意味.Option<i32> なら「i32 があるかないか」,Option<String> なら「String があるかないか」.

fn find_even(numbers: Vec<i32>) -> Option<i32> {
    for n in numbers {
        if n % 2 == 0 {
            return Some(n);
        }
    }
    None
}

fn main() {
    let nums = vec![1, 3, 4, 7];
    let result = find_even(nums);

    match result {
        Some(n) => println!("found: {}", n),
        None => println!("not found"),
    }
}

Option の扱い方

unwrap: 「絶対ある!」

fn main() {
    let x: Option<i32> = Some(42);
    let value = x.unwrap(); // 42
    println!("{}", value);
}

None に対して unwrap() するとプログラムが落ちる(パニック).「絶対に Some のはず」という確信があるときだけ使う.

expect: unwrap + メッセージ付き

fn main() {
    let x: Option<i32> = Some(42);
    let value = x.expect("値がないとおかしい");
    println!("{}", value);
}

unwrap と同じだけど,落ちたときのメッセージを指定できる.

match: 安全に処理

fn main() {
    let x: Option<i32> = Some(42);

    match x {
        Some(v) => println!("got: {}", v),
        None => println!("nothing"),
    }
}

if let: マッチが 1 パターンだけのとき

fn main() {
    let x: Option<i32> = Some(42);

    if let Some(v) = x {
        println!("got: {}", v);
    }
}

None のときは何もしない場合に便利.

Result: 成功か失敗か

Result は処理が成功したか失敗したかを表す:

enum Result<T, E> {
    Ok(T),   // 成功(値は T)
    Err(E),  // 失敗(エラーは E)
}
fn divide(a: f64, b: f64) -> Result<f64, String> {
    if b == 0.0 {
        Err(String::from("division by zero"))
    } else {
        Ok(a / b)
    }
}

fn main() {
    match divide(10.0, 3.0) {
        Ok(result) => println!("result: {}", result),
        Err(e) => println!("error: {}", e),
    }

    match divide(10.0, 0.0) {
        Ok(result) => println!("result: {}", result),
        Err(e) => println!("error: {}", e),
    }
}

Result も unwrap できる

fn main() {
    let result = divide(10.0, 3.0);
    let value = result.unwrap(); // 3.333...
    println!("{}", value);
}

Err に対して unwrap() するとパニック.Option と同じルール.

? 演算子: エラーを上に投げる

関数の中で ? を使うと,Err だった場合にそのまま呼び出し元に返してくれる:

fn calc(a: f64, b: f64, c: f64) -> Result<f64, String> {
    let ab = divide(a, b)?;  // Err なら即 return
    let result = divide(ab, c)?;
    Ok(result)
}

fn divide(a: f64, b: f64) -> Result<f64, String> {
    if b == 0.0 {
        Err(String::from("division by zero"))
    } else {
        Ok(a / b)
    }
}

fn main() {
    match calc(100.0, 5.0, 2.0) {
        Ok(v) => println!("result: {}", v),
        Err(e) => println!("error: {}", e),
    }
}

? は「成功なら値を取り出す,失敗ならこの関数から即 Err を返す」という意味.戻り値が Result の関数でしか使えない.

よく見るパターン

標準ライブラリの多くの関数が Result を返す:

fn main() {
    // 文字列 → 数値の変換(失敗するかもしれない)
    let num: Result<i32, _> = "42".parse();
    println!("{}", num.unwrap()); // 42

    let bad: Result<i32, _> = "hello".parse();
    println!("{}", bad.is_err()); // true
}

_ は「型を推論に任せる」という意味.

まとめ

意味 中身
Option<T> あるかないか Some(T) / None
Result<T, E> 成功か失敗か Ok(T) / Err(E)

扱い方:

やり方 いつ使う
match 安全に両方のケースを処理したいとき
if let 片方だけ処理すれば十分なとき
unwrap() / expect() 絶対に Some/Ok のはずというとき
? エラーを呼び出し元に投げたいとき

おめでとう.Rust の基礎文法はこれで完了. 次の章からは,学んだ知識を使って四則演算の電卓を作っていく.

本に戻る