2024/04/14

前回、プログラミング言語にはガーベジコレクション(GC)を持つ言語と持たない言語があると書いた。まずはざっと前回のおさらいをしておこう。GCのないC言語などでは、プログラマがメモリの確保・解放をコードに書く必要があるが、適切に書けば高速に動く。その代わり、見つけにくいメモリーバグを内包するリスクがある。一方、GCのあるpythonなどの言語では、メモリのことを気にせずコードを書くことができる半面、GCによる効率低下という代償を払う必要がある。ただし、ハードウェアの高速化・大容量化で効率低下が気にならなくなってきて、pythonなどのスクリプト言語が人気になっている。
そのようなの中で、やっぱりコンパイル型でGCを持たず、かつメモリー安全な言語がほしいと思っている人は根強く居たらしく、MozillaのチームがRustという新しい言語を作ってくれた。Rustは、GCを持たず、C言語と同様に実行速度の速いコンパイラ型に分類される言語だが、言語仕様に独自の工夫を加えることで、メモリー安全な言語になっている。以下にその独自の工夫を紹介する。
Rustでは、プログラムで使われる各値(=メモリー上のデータ)と、それに名前をつけて保持しておくための変数に対して、所有権規則というルールを課している。
<Rustの所有権規則>
・Rustの各値は、所有者と呼ばれる変数と対応している。
・いかなる時も所有者は一つである。
・所有者がスコープから外れたら、値は破棄される。
単純な規則だが、この規則を一見しただけでは、プログラムを書いたことのない人はもちろん、C言語やPythonのプログラマにとっても、最初は、どういうこと?と戸惑うかもしれない。この規則を「なるほど」と理解するには、残念ながら、実際にRustでプログラムを書いてみるしかない。
とは言え、なぜこの規則で、メモリー安全なプログラムが書けるのかを、雰囲気だけでも伝えてみたい。それは、C言語でのメモリーバグの原因が、同じメモリーを複数の変数(仮にaとbとする)が参照している状態で起こることがほとんどだという理由による。プログラムのある部分で、1つの変数aを使い終わったので解放したら、次に新たなメモリー割り当ての要求があると、OSはその領域を別の変数(c)のために与えてしまう。ここで、もともとaと同じメモリーを参照していた変数bを使った処理が動いたらどうなるだろう。bが使いたい領域は、既に別の変数cが使っているので、bは予期せぬ中身を見ることになる。更に、bがそのメモリーを解放してしまったら(二重解放)、cも予期せぬデータを扱うことになり、たいて、そのプログラムは異常終了する。
Rustの所有権規則は、このようなメモリー異常が決して発生しないようにガードしてくれる。2番目のルールで、そもそも、aとbは同じメモリーを参照できない(*)。aが使っていたデータをbも使いたいなら、そのデータは、aからbに所有権を移動させなければならない。そして、プログラムがaを使い終わったら、その利用範囲(スコープと呼ぶ)を抜けるときに、Rustがそのメモリーを解放してくれる。もし、上記のようにそのデータの所有権が、aからbに移動したら、その時点で変数aは何も所有してないので、aがスコープを抜けた時には、Rustは何もしなくてよい。このようなしくみで、C言語が内在していたメモリーの二重解放や、不定領域への参照がなくなる。
最後に、借用について書いておこう。プログラムを書いていると、一時的にデータを借りて使いたい場合がある。そのような時に一時的にデータを借りるしくみを借用と呼ぶ。Rustでは借用も厳しく管理され、書き換え可能な参照はある1つの時点で1回だけと決められている。
このような、所有権・移動・借用と言った規則は、プログラマにとってはわずらわしい。pythonやJavaでプログラムを書いていた人にとっては、GCが全て面倒見てくれていて、何も考えなくてもよかったことを、Rustコンパイラの警告として指摘されることになる。しかし、人間の側がコンピュータのメモリに歩み寄って、このような面倒なルールの元でコードを書くことで、GCのない高速なシステムを作ることができ、かつメモリーバグに陥ることがない。その恩恵を考えると、この面倒なルールも、まぁ、受け入れられるように思う。そう思う人が多いので、Rustの人気が出てきているのだろう。
(*)不変な借用であれば、複数個所から同じメモリーを参照できる状態を作ることはできるが、その際も所有者となる変数は1つだけというルールは厳密に適用され、メモリーを破壊することはない。
2022.4.8(金)
写真:Rustのロゴ