nullはポインタだがリファレンスではない

null ポインタの存在は正当化できるかもしれないが、null 参照の存在は正当化できないのでは、という考え。

null はポインタであってもよかったかもしれない

C言語はポインタ型の値として、脱参照できないポインタ (*p の読み書きが UB になるような p) を認めている。 これは「長さゼロの動的配列を指すポインタ」や「配列の終端を指すポインタ (&array[len(array)])」が脱参照できないことを考えれば、そのようなポインタの存在自体を認めないわけにはいかないことに納得がいく。

つまりポインタ型は、どこかを指しているが、そこに要素があるとまでは保証していない型とみなせる。 そう考えると null ポインタがポインタ型の一種として認められていても全体として破綻はない。

(リテラルの 0 が null ポインタになるという謎の変換はともかく。後知恵でいえば、整数とポインタ型の間の暗黙のキャストはなしにして、null ポインタがほしいときは (void*)0 というように明示的なキャストを要求するか、いまの C++ のように nullptr キーワードを入れておくべきだった。)

null はリファレンスであるべきではなかった

一方、Java や C# などの言語には参照 (リファレンス) というポインタ的なものがあって、null を代入でき、null だけ「ポインタの脱参照に相当する操作」(フィールドアクセスやメソッドの呼び出しなど)ができない。 脱参照できないリファレンスは null だけであって、そのようなリファレンスが存在しなければ絶対いけないという事情はないはず。

Java 界隈の事情はよく知らないが C# に関していえば、default(T) (型 T の既定値) はビットパターンが 0 な値になってほしいという事情がある。配列を確保するとき領域をゼロ埋めするだけで済むため。 とはいえ null に起因するもろもろの不都合と比べてどっちがよいかという話であって、null は始めから除いておいてほしかった。 実際、C# 8 から参照型は null を持てないようになる。(参照型 T は null を持てなくなり、従来の null 許容な参照型は T? と書かなければいけなくなる、という機能。参考: null 許容参照型.)

配列を確保するときに初期値をどうするかは難しい。Rust には null ポインタが (unsafe でない範囲では) 存在しない。配列を確保するときは [初期値; 長さ] の形で書き、初期値を指定する。初期値を1つずつ埋めていく処理は最適化で消せるかもしれない。あるいは、動的配列である Vec 型 (.NET の List に相当) をキャパシティつき・長さゼロで確保して、要素を追加していく。

関連記事