やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

NULL参照は10億ドルの失敗だった

 NULLのせいで数々のバグが生まれ膨大な損失を被った、という話をRust学習中にはじめて知った。

参照

NULL開発者トニー・ホーア氏

私はそれを10億ドルの失敗と呼んでいます。その頃、私は、オブジェクト指向言語の参照に対する、 最初のわかりやすい型システムを設計していました。私の目標は、 どんな参照の使用も全て完全に安全であるべきことを、コンパイラにそのチェックを自動で行ってもらって保証することだったのです。 しかし、null参照を入れるという誘惑に打ち勝つことができませんでした。それは、単純に実装が非常に容易だったからです。 これが無数のエラーや脆弱性、システムクラッシュにつながり、過去40年で10億ドルの苦痛や損害を引き起こしたであろうということなのです。

NULL安全

 コンパイルの時点でNULL参照が起こりうるコードに対してエラーを吐く。

 これにより「10億ドルの損失」を未然に防ぐ。

NULLは時に有用

  • 何らかの理由により現在は無効であることを示す
  • 存在しない値であることを示す

 NULLは特定の実装において存在する。完全に排除すると不都合が生じる。

Rustでの解決

 Rustでは基本的にNULLを使わない。でも使う方法もある。そのときはNULL参照エラー(10億ドルの損失)に対して、確実かつ迅速に発見できる仕組みになっている。また、タイプ数を少なく書ける糖衣構文もある。

std::option::Option

 RustではNULLを表現するためenumOption<T>がある。

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

型エラー

let value: Option<i32> = None;
value = Some(100);

let x: i32 = 5;
let sum = x + value; // error[E0277]: the trait bound `i32: std::ops::Add<std::option::Option<i32>>` is not satisfied

 i32型とOption<i32>型は別モノなのでエラーになる。i32型にはOption型の値であるNone,Some(i32)を代入できない。また、それら別型を演算することも不可。

 つまり、NULL(None)を扱いたいならOption型として明示せねばならない。このおかげでNULL参照エラー(10億ドルの損失)を回避できる。

Match

Matchの包括的パターンエラー

fn main() {
    let x: Option<i32> = None;
    x = Some(0);
    match x { // error[E0004]: non-exhaustive patterns: `None` not covered
        Some(i) => print("{}", i),
    }
}

 上記では、matchはとりうる値の範囲が網羅されていないというエラーになってくれる。つまりmatchを使えば取りこぼしせずに済む。

fn main() {
    let x: Option<i32> = None;
    x = Some(0);
    match x {
        Some(i) => print("{}", i),
        None => (),
    }
}

 Option型はSomeNoneをとりうることがその型定義からわかる。matchはそれを慮って、パターン網羅性チェックしてくれる。

 NULL(None)についても同様なので、実行時にNULL参照エラーが起こり得ない仕組み。

_プレースホルダ

fn main() {
    let x: Option<i32> = None;
    x = Some(0);
    match x {
        Some(100) => print("{}", i),
        _ => (), // None, Some(100以外すべて)
    }
}

 switch文でいうdefaultのようなもの。

マッチガード

fn main() {
    let x: Option<i32> = None;
//    let x: Option<i32> = Some(0);
//    let x: Option<i32> = Some(-1);
//    let x: Option<i32> = Some(1);
    match x {
        Some(i) if 0 > i => println!("負数 {}", i), // -1以下の整数
        Some(i) if 0 < i => println!("自然数 {}", i), // 0を含まない正の整数
        _ => (), // None, 0 のいずれか
    }
}

 Some(i)だとiの値が何であれマッチしてしまう。これをマッチガードによって細かい条件付けをする。

 Some(i)の後ろにif文がある。これがマッチガード。

 マッチガードと_を駆使すれば、None0のいずれかの場合を表現できる。なお、None | Some(0) => (),と表記することも可。

対象環境

$ uname -a
Linux raspberrypi 4.19.42-v7+ #1219 SMP Tue May 14 21:20:58 BST 2019 armv7l GNU/Linux

前回まで