// dictionary
所有権.
所有権(Ownership)は、Rustのメモリ管理を支える中核の仕組みです。すべての値は、それを保持する変数など、ただ1つの「所有者(owner)」を持ちます。所有者がスコープを抜けると値は自動で破棄され、メモリが解放されます。
この仕組みにより、Rustはガベージコレクション(GC)にも手動のメモリ解放にも頼りません。所有権のルールはすべてコンパイル時に検査され、違反したコードはコンパイルエラーになります。
fn main() {
{
let receipt = String::from("レシート: 弁当 500円");
println!("{receipt}"); // このスコープ内では receipt が値を所有している
} // ここで所有者 receipt がスコープを抜け、値は自動で破棄される
// println!("{receipt}"); // ここでは receipt はもう存在しない(エラー: E0425)
}Playgroundで開く所有権の3つのルール
- Rustのすべての値は、「所有者」を持ちます
- 所有者は常にただ1つです(同じ値を2つの変数が同時に所有することはありません)
- 所有者がスコープを抜けると、値は破棄(drop)されます
所有者は固定ではなく、変数への代入や関数への受け渡しで別の変数へ移動します(ムーブ)。また、所有権を移動させずに値を利用する手段として参照があります(参照を作って値を借りることを借用と呼びます)。
なぜ所有権が必要なのか
ヒープ領域に確保したメモリ(文字列型の内容など)は、いつか誰かが解放しなければなりません。主要な言語のアプローチと比べると、所有権の位置づけが分かります。
| 方式 | 採用例 | 課題 |
|---|---|---|
| プログラマが手動で解放 | C / C++ | 解放忘れ・二重解放・解放後アクセスを人間が防ぐ必要がある |
| GCが実行時に回収 | Java / Go など | 実行時のオーバーヘッドがあり、回収タイミングを制御しにくい |
| 所有権(コンパイル時に解放点を決定) | Rust | ルールに従うコードしかコンパイルが通らない(学習コスト) |
「所有者は常に1つ」「所有者がスコープを抜けたら破棄」というルールがあるため、コンパイラはどのメモリをどこで解放すべきかを原則コンパイル時に決定できます。その結果、二重解放や解放済みメモリへのアクセスといったバグはコンパイルエラーとして弾かれ、解放忘れ(メモリリーク)も通常のコードでは起こらなくなります。しかもGCのような実行時コストは発生しません。
補足
C言語ではどう不具合になるのか(二重解放の例)
手動解放のC言語では、確保したメモリをfreeで解放するタイミングをすべてプログラマが管理します。ここで厄介なのは、「誰がそのメモリを解放するのか」という責任者がコード上のどこにも現れず、コンパイラも関知しないことです。関数にポインタを渡すと、呼び出し元と関数側の両方が同じメモリを指す状態になります。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 注文メモを印字し、用済みになったメモリを解放する関数
void print_order(char *order) {
printf("%s\n", order);
free(order); // この関数がメモリを解放する
}
int main(void) {
char *order = malloc(32); // 注文メモ用のメモリを確保
strcpy(order, "コーヒー ×2");
print_order(order); // 関数の中で order の指すメモリが解放される
free(order); // 解放済みと知らずに再度解放(二重解放・未定義動作)
return 0;
}
print_orderが「印字して後始末までする」つもりで書かれている一方、main側は「自分で確保したメモリは自分で解放する」つもりでいる——どちらも単体では自然なコードですが、組み合わさると同じメモリを2回解放してしまいます。このコードはエラーにならずコンパイルが通ってしまい、問題は実行時に初めて表面化します(近年のコンパイラはこの程度の単純な例なら警告できることもありますが、ポインタの受け渡しが複雑になると追跡できず、いずれにせよ止めてはくれません)。しかも結果は「未定義動作」で、即クラッシュすることもあれば、無関係なデータを壊しながら一見正常に動き続けることもあり、原因の特定が非常に困難です。防ぐには「渡した先の関数が解放するのか、呼び出し元が解放するのか」という約束事を、コンパイラの助けなしにコード全体で守り続けるしかありません。
Rustの所有権はまさにこの管理をコンパイラに肩代わりさせる仕組みです。「値の所有者はただ1つ」と決まっているため解放の責任者が常に明確で、関数に値を渡せば所有権ごと移動し、渡した後に呼び出し元が値を使おうとするコードはそもそもコンパイルが通りません。
破棄の実体はdrop
所有者がスコープを抜けるとき、Rustはその値のdropという関数を自動で呼び出します。閉じ波括弧}の位置で自動的に後始末が走るこの仕組みは、C++でリソースの確保と解放をオブジェクトの寿命に結びつけるRAII(Resource Acquisition Is Initialization)パターンに相当します。ファイルハンドルやネットワーク接続など、メモリ以外のリソースの解放にも同じ仕組みが使われます。なお、このdropを手動で直接呼ぶことはできません(エラー: E0040。スコープの途中で明示的に破棄したい場合はstd::mem::dropを使います)。
また、メモリリークの完全な防止はRustの保証には含まれません。std::mem::forgetやRcの循環参照のように、安全なコードでも意図的・偶発的にリークを起こす手段は残されています。
この辞書が使われているページ
backlinks 11