なぜ所有権が必要なのか
CやC++ではメモリ管理を開発者が手動で行うため、こんなバグが起きます。
- ダングリングポインタ: 解放済みのメモリへのアクセス
-
二重解放: 同じメモリを2回
free()してしまう - メモリリーク: 確保したメモリを解放し忘れる
JavaやPythonはGCがメモリを自動管理しますが、実行時にGCが動くため予測できないパフォーマンス低下が発生します。
RustはGCを使わずに、コンパイル時に所有権ルールを機械的にチェックすることで、実行時コストゼロでメモリ安全を実現しています。
所有権の3つのルール
Rustのメモリ管理はこの3つのルールだけで成り立っています。
1. 各値は必ず1つの所有者(変数)を持つ
2. 所有者は同時に1つしか存在できない
3. 所有者がスコープを抜けると、値は自動的に解放される
これだけです。あとはこのルールをコンパイラが機械的に適用しているだけです。
ルール1・2:所有権のムーブ
変数間の代入
fn main() {
let s1 = String::from("hello"); // s1 が "hello" の所有者
let s2 = s1; // 所有権が s1 → s2 へ移動(ムーブ)
// println!("{}", s1); // ❌ コンパイルエラー!s1 はもう無効
println!("{}", s2); // ✅ s2 は有効
}
メモリのイメージ:
ムーブ前: s1 → [ ヒープ上の "hello" ]
ムーブ後: s1 → 無効(ポインタを持たなくなる)
s2 → [ ヒープ上の "hello" ]
所有権は同時に1人しか持てないため、s2 = s1 した時点で s1 は無効になります。これにより同じメモリを2回解放する「二重解放」が防がれます。
関数呼び出し時にもムーブは起きる
代入だけでなく、関数に値を渡したときにも所有権がムーブします。
fn take_ownership(s: String) {
println!("{}", s);
} // ← ここで s のメモリが自動解放される
fn main() {
let s1 = String::from("hello");
take_ownership(s1); // s1 の所有権が関数に移る
// println!("{}", s1); // ❌ s1 はもう無効
}
関数から値を戻り値として返すと所有権が呼び出し元に戻ります。
fn give_ownership() -> String {
let s = String::from("world");
s // 戻り値として返すことで所有権を呼び出し元に移す
}
fn main() {
let s1 = give_ownership(); // 所有権を受け取る
println!("{}", s1); // ✅ 使える
}
ルール3:スコープを抜けると自動解放
fn main() {
{
let s = String::from("world"); // s がスコープに入る
println!("{}", s); // ✅ 使える
} // ← ここで s のメモリが自動解放される
// println!("{}", s); // ❌ s はもう存在しない
}
Rustはスコープを抜けるタイミングで自動的に drop() を呼び出し、ヒープ上のメモリを解放します。GCのような実行時オーバーヘッドなしに、決定論的なタイミングでメモリが解放されます。
Copy 型:ムーブされない例外
整数などスタックだけで完結する型はムーブではなくコピーされます。
fn main() {
let x = 5;
let y = x; // x はコピーされる(ムーブではない)
println!("x={}, y={}", x, y); // ✅ 両方使える!
}
なぜ Copy 型と非 Copy 型があるのか
| 型の種類 | 例 | 動作 | 理由 |
|---|---|---|---|
| Copy 型 |
i32, f64, bool, char
|
自動コピー | スタックのみ。固定サイズで複製コストが低い |
| 非 Copy 型 |
String, Vec, Box
|
ムーブ | ヒープを使う。コピーには新規メモリ確保が必要 |
String を自動コピーにすると、代入のたびにヒープ確保が発生して非常に遅くなります。だからRustはデフォルトをムーブにして、明示的に clone() を呼んだときだけコピーする設計にしています。
借用(Borrowing):所有権を渡さずに使う
毎回所有権を渡して戻してを繰り返すのは面倒です。そこで**借用(参照)**を使います。
fn print_length(s: &String) { // & で参照を受け取る(借用)
println!("長さ: {}", s.len());
}
fn main() {
let s1 = String::from("hello");
print_length(&s1); // & で参照を渡す
println!("{}", s1); // ✅ s1 は所有権を保持したまま使える
}
借用のイメージ:
s1 → [ "hello" ] ←── &s1(参照)
↑
print_length が借りている間は読めるが
所有権は s1 が持ち続ける
関数が終わったら参照は消え、s1 は継続して有効
不変借用は同時に何個でもOK
fn main() {
let s = String::from("hello");
let r1 = &s;
let r2 = &s;
let r3 = &s; // ✅ 不変借用はいくつでも同時に持てる
println!("{}, {}, {}", r1, r2, r3);
}
複数の読み取りは安全なので制限がありません。
可変借用:1つだけ
値を変更するには**可変借用(&mut)**が必要です。ただし同時には1つだけです。
fn main() {
let mut s = String::from("hello");
let r1 = &mut s;
// let r2 = &mut s; // ❌ 同時に2つの可変借用は不可
r1.push_str(", world");
println!("{}", r1); // ✅ hello, world
}
借用の組み合わせルール
不変借用 (&s) : 同時に何個でもOK
可変借用 (&mut s): 同時に1個のみ。不変借用とも共存不可
✅ &s と &s → OK(読み取りは複数OK)
❌ &s と &mut s → NG(読み取りと書き込みの同時は不可)
❌ &mut s と &mut s → NG(書き込みは1つだけ)
この制限により、データ競合がコンパイル時に防がれます。並行処理でよく起きる「同時に読み書きしてデータが壊れる」問題を、ランタイムなしで検出できるのがRustの強みです。
よくあるコンパイルエラーと対処
| エラー | 原因 | 対処 |
|---|---|---|
value moved here |
ムーブ済みの変数を使った |
clone() するか借用に変える |
cannot borrow as mutable |
不変変数を変更しようとした |
let mut にする |
cannot borrow ... more than once as mutable |
可変借用を2つ作った | 借用のスコープをずらす |
does not live long enough |
参照が指す値が先に解放された | 値のライフタイムを延ばす |
// ❌ ムーブ後に使おうとした例
let s1 = String::from("hello");
let s2 = s1;
println!("{}", s1); // error[E0382]: borrow of moved value: `s1`
// ✅ パターン1: clone() でヒープごとコピー
let s1 = String::from("hello");
let s2 = s1.clone();
println!("{} {}", s1, s2); // 両方使える
// ✅ パターン2: 借用を使う(コピーコストなし)
let s1 = String::from("hello");
let s2 = &s1; // 参照を借りるだけ
println!("{} {}", s1, s2); // 両方使える
まとめ
| 概念 | 一言で言うと | よくある間違い |
|---|---|---|
| 所有権 | 値は1人しか持てない。スコープを出たら自動解放 | 関数に渡した後に使おうとする |
| ムーブ | 代入・関数渡しで所有権が移り、元の変数は無効 | 移動後の変数を使う |
| Copy | 整数など固定サイズの型は自動コピー(ムーブしない) | String もコピーされると勘違い |
| 借用(&) | 所有権を渡さず一時的に貸し出す(複数OK) | 所有権を渡す代わりに使う |
| 可変借用(&mut) | 変更可能な借用。同時に1つだけ | 複数作ろうとする |
この5つを押さえると、Rustのコンパイラエラーの大半は自分で解読できるようになります。
この記事の内容をさらに深く学びたい方へ
所有権は Rust の核心ですが、実際にコードを書いて手を動かすことで初めて体に馴染みます。
以下の講座では、バグのあるコードを直す演習スタイルで、所有権・借用・トレイト・エラー処理を段階的に習得できます。環境構築は不要、ブラウザだけで今すぐ始められます。
関連講座
| 講座 | 内容 |
|---|---|
| 【Rust入門決定版】環境構築0分!Playgroundで手を動かしながら学ぶ挫折しないプログラミング講座 | 所有権・借用・トレイト・エラー処理を演習形式で完全習得(日本語) |
| Rust for Beginners: Start Coding Instantly in the Playground | Same content in English — learn Rust without any local setup |
講座まとめ
その他のWeb・AI・プログラミング講座は以下でまとめて確認できます。
👉 講座一覧サイト
👉 Udemyプロフィール