LoginSignup
8
2

More than 5 years have passed since last update.

yet another 「例外を投げない」言語

Last updated at Posted at 2019-03-08

「例外を投げない」という選択肢をとる言語

という記事がトレンドに乗ってたので・・

Rustなのか?Rustなのかい?

って思って開いてみた所・・・

ok, err := foo()

ってやった場合と

val, ok := bar["key"]

みたいにやった時に何がokなんだかよくわかんなくなる方の言語でした。
いや、使い方が悪いんでしょうけど。
最初、if val, ok := bar["key"]; ok{ ...みたいなコード見て混乱した覚えがあります。keyがあれば値が入るし、keyがないとfalseが返ってくる。ちょっと気持ち悪い。

ってごめんなさい。Goも好きです。disってないです。

Rustの場合

で、自分的に「例外を投げない」でピンと来た方がこちら。
Rust初心者なのですが生意気にすいません。
Rustも例外のない言語です。
仕様的にはGoに近くて多値返却ではあるんですが、その多値を受け取る型が用意されています。
失敗する可能性のある関数だとすんなりとString型とかを返却する事はなくて、そのStringをなんらかの形でwrapして返す。
なので受け取ったStringを使いたかったらそのなんらかの形から取り出して使うしかない。

Rustのエラーハンドリング
https://doc.rust-jp.rs/the-rust-programming-language-ja/1.6/book/error-handling.html

Result型

こういう感じの列挙型です

pub enum Result<T, E> {
    Ok(T),
    Err(E),
}

例えばファイルを開くstd::fs::File::open は開こうと思ったファイルの存在やら権限やらによっては失敗する関数です。
シンプルに開くだけの例だとこんな感じです。開こうとするとResultのenumが返却されるので、OkのパターンとErrのパターンに対応できる。

use std::fs;

fn main() {
    //returns `io::Result<File>`
    match fs::File::open("foo.txt") {
        Ok(_f) => println!("opened foo.txt "),
        Err(_e) => panic!("couldn't open foo.txt "),
    }
}

Option型

Someってなんやねん。判りづらい。。
正常な値が存在すればstd::option::Option::Some を介して取り出せるって考えてればいいんだと思います。

pub enum Option<T> {
    None,
    Some(T),
}

例えば、こんな感じでゼロ除算するとNoneの値が返って、それ向けに書いた処理が実行されます。
正常に割り切れたものは当然正常な処理が実行される。

// An integer division that doesn't `panic!`
fn checked_division(dividend: i32, divisor: i32) -> Option<i32> {
    if divisor == 0 {
        // Failure is represented as the `None` variant
        None
    } else {
        // Result is wrapped in a `Some` variant
        Some(dividend / divisor)
    }
}

// This function handles a division that may not succeed
fn try_division(dividend: i32, divisor: i32) {
    // `Option` values can be pattern matched, just like other enums
    match checked_division(dividend, divisor) {
        None => println!("{} / {} failed!", dividend, divisor),
        Some(quotient) => {
            println!("{} / {} = {}", dividend, divisor, quotient)
        },
    }
}

fn main() {
    try_division(4, 2);
    try_division(1, 0);
}

//https://doc.rust-lang.org/rust-by-example/std/option.html

実行するとこんな感じに。

$ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
     Running `target/debug/rusttest`
4 / 2 = 2
1 / 0 failed!

こんな感じでいちいち値がwrapされる。
ちなみにこういう感じでwrapされててもunwrapとか使うと簡単に取り出す事はできる。
アンチパターンになりがちだけど、あっさり書いてもいい場所には使える。
expect でそのままpanicにしちゃったりもできる。
https://doc.rust-lang.org/std/option/enum.Option.html#method.unwrap

let x = Some("air");
println!("{}", x.unwrap());

std::collections::HashMap.get()

HashMapでgetしたときもOptionが返ってきます。
unwrap()しちゃうとこんな感じ。

use std::collections::HashMap;

fn main(){
    let mut map = HashMap::new();
    map.insert("foo", "bar");

    println!("value is {}",map.get(&"foo").unwrap());
}

Rustややこしい

Rustの 3大ややこしい のうちの一つがこのエラー処理なのではなかろうか。
他の言語でぜんぜん馴染みないし。
でもプログラマがエラーを強制的に意識して書かなくてはいけないってのは安全なコードにつながるし、Rustの思想なんだと思う。

補足

例外はないけど、Dropトレイトっていうデストラクタはあるみたいです。try~catch的な事もできるかもしれないです。原則として後始末用なんだと思いますが。

8
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
2