NULLのせいで数々のバグが生まれ膨大な損失を被った、という話をRust学習中にはじめて知った。
参照
NULL開発者トニー・ホーア氏
私はそれを10億ドルの失敗と呼んでいます。その頃、私は、オブジェクト指向言語の参照に対する、 最初のわかりやすい型システムを設計していました。私の目標は、 どんな参照の使用も全て完全に安全であるべきことを、コンパイラにそのチェックを自動で行ってもらって保証することだったのです。 しかし、null参照を入れるという誘惑に打ち勝つことができませんでした。それは、単純に実装が非常に容易だったからです。 これが無数のエラーや脆弱性、システムクラッシュにつながり、過去40年で10億ドルの苦痛や損害を引き起こしたであろうということなのです。
NULL安全
コンパイルの時点でNULL参照が起こりうるコードに対してエラーを吐く。
これにより「10億ドルの損失」を未然に防ぐ。
NULLは時に有用
- 何らかの理由により現在は無効であることを示す
- 存在しない値であることを示す
NULLは特定の実装において存在する。完全に排除すると不都合が生じる。
Rustでの解決
Rustでは基本的にNULLを使わない。でも使う方法もある。そのときはNULL参照エラー(10億ドルの損失)に対して、確実かつ迅速に発見できる仕組みになっている。また、タイプ数を少なく書ける糖衣構文もある。
std::option::Option
RustではNULLを表現するためenum型Option<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
型にはOptionNone
,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 => (), } }
OptionSome
とNone
をとりうることがその型定義からわかる。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
文がある。これがマッチガード。
マッチガードと_
を駆使すれば、None
と0
のいずれかの場合を表現できる。なお、None | Some(0) => (),
と表記することも可。
対象環境
- Raspbierry pi 3 Model B+
- Raspbian stretch 9.0 2018-11-13
- bash 4.4.12(1)-release
$ uname -a Linux raspberrypi 4.19.42-v7+ #1219 SMP Tue May 14 21:20:58 BST 2019 armv7l GNU/Linux
前回まで
- Rustを学んでみたい(プログラミング言語)
- Rustの環境構築
- RustでHelloWorld
- Rustの和訳ドキュメント
- Cargoでプロジェクト作成・ビルド・実行
- クレートとは?
- Rustで関数を使ってみる
- Rustでモジュールを使ってみる
- Rustで乱数を生成する(rand)
- Rustで標準入力する(std::io::stdin().read_line())
- RustでMatch判定する(match)
- Rustでprintとread_lineを1行にする方法
- Rustで数当てゲーム
- クレート名にドット.が使えない
- Rustの変数と可変性(let, mut) error[E0384]: cannot assign twice to immutable variable
x
- Rustのimmutable束縛とconst定数の違い
- RustのREPL、evcxrのインストールに失敗した
- Rustでコンパイルするときの変数未使用warningを消す
- Rustの変数(再代入、再宣言(シャドーイング))
- Rustのシャドーイングについて
- イミュータブルについて(副作用)
- Rustの定数(const)
- Rustのデータ型(数値)
- Rustのデータ型(論理)
- Rustのデータ型(文字)
- Rustのデータ型(タプル)
- Rustのデータ型(配列)
- Rustの関数
- Rustのif式
- Rustのくりかえし文(loop)
- Rustのくりかえし文(while)
- Rustのくりかえし文(for)
- Rustの所有権(ムーブ)
- Rustの所有権(関数)
- Rustの所有権(スライス)
- Rustの構造体(定義とインスタンス化)
- Rustの構造体(プログラム例)
- Rustの構造体(メソッド)
- Rustの列挙型(enum)
- Rustの列挙型(enum)
- Rustの列挙型(enum)
- Rustのmatch(制御フロー演算子)
- RustでNULLを扱う(Option, Some, None)