3.2K WORDS · KOHTA KOUCHI
Rustの所有権と借用を理解する
Rustを他の言語と分けている最大の特徴——ガベージコレクションなしで、コンパイル時にメモリ安全性を保証する仕組み。
Rustが他の多くの言語と違う最大の特徴は、その所有権システムだ。ガベージコレクションを動かすことなく、コンパイル時にメモリ安全性を保証する。仕組みは三つのルールに集約される。まず、すべての値はただ一人のオーナーを持つ。String を別の変数に代入すると所有権がそちらへ移動(ムーブ)し、元の変数は無効になる。このムーブセマンティクスが、同じメモリを二箇所から所有してしまう事故を防ぐ。
二つ目のルール。オーナーがスコープを抜けると、その値は自動的にドロップ(解放)される。関数やブロックを出れば、メモリは勝手に片付く——手動の free() も、リークもない。三つ目。値を関数に渡すと、所有権はその関数へ移動する。だから「いま誰がこの値に責任を持っているか」が常に曖昧にならない。
所有権だけだと値の共有がつらいので、Rust には借用がある。不変借用(&)は値を読み取り専用で貸し出す——借り手は読めるが変更はできない——ので、オーナーが所有権を保ったまま複数の読み手が同時に参照できる。可変借用(&mut)は、借り手が本当に値を書き換える必要があるとき、たとえば String に文字列を追加するようなときに使う。
全体を縛るルールはこれだ。不変借用と可変借用は混在できない。任意の時点で、複数の不変借用か、ただ一つの可変借用か、どちらか一方しか成立しない。このたった一つの制約が、データ競合を原理的に不可能にする。Rust 2018 以降の Non-Lexical Lifetimes のおかげで、借用の有効範囲は「最後に使われたところまで」になり、レキシカルなスコープの終わりを待たずに次の借用を始められる。
コンパイラが参照の有効期間を推論できないときは、ライフタイム('a)で明示する。たとえば二つの参照を受け取って一つを返す関数は、戻り値がどちらの入力と同じ期間生きるのかを書く必要がある。ダングリング参照もコンパイラが許さない。スコープを抜ける値への参照を返そうとすればコンパイルエラーになり、代わりに所有権ごと返すことになる。そして clone() より借用を選ぶこと。クローンは複製なのでコストがかかるが、借用は参照を渡すだけだ。
これらのルールが身体に入ると、メモリ安全性・GC不要のパフォーマンス・スレッド安全性・明示的なメモリ管理が一度に手に入る。最初は窮屈に感じるが、コンパイラは結局のところ、どのみち下さなければいけない判断を一緒に歩いてくれて、しかもバグを出荷前に捕まえてくれているだけなのだ。