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