Rustの所有権とは何か?
Rustを学び始めたとき、多くの人がつまずくのが「所有権(Ownership)」の概念です。C言語のように手動でメモリを管理する必要もなく、GCのように実行時にメモリを回収する仕組みもない——それがRustの独自性です。
Rustはコンパイル時に所有権のルールを検証することで、メモリ安全性を保証しています。
所有権の基本ルールは以下の3つです。
- Rustの各値は、**所有者(owner)**と呼ばれる変数を持つ
- 所有者は同時に1つしか存在できない
- 所有者がスコープから外れると、値は自動的に解放される
fn main() {
let s = String::from("hello"); // s が "hello" の所有者
println!("{}", s);
} // ここで s はスコープを抜け、メモリが自動解放される
String::from はヒープにメモリを確保します。スコープを抜けると drop が自動で呼ばれ、メモリが解放されます。GCなしで安全なメモリ管理が実現されているのです。
ムーブセマンティクス:所有権の移動
所有権は別の変数に**移動(ムーブ)**することができます。ただし、ムーブ後は元の変数を使えなくなります。
fn main() {
let s1 = String::from("hello");
let s2 = s1; // 所有権が s1 から s2 へムーブ
// println!("{}", s1); // コンパイルエラー!s1 はもう使えない
println!("{}", s2); // OK
}
これは「同時に2人が同じデータを所有する」ことを防ぐためのルールです。C++でありがちな「ダブルフリー」(同じメモリを2度解放するバグ)をコンパイル時に防いでいます。
関数への引数渡しでもムーブが起きる
fn print_string(s: String) {
println!("{}", s);
} // s はここで解放される
fn main() {
let s = String::from("world");
print_string(s); // 所有権が関数にムーブ
// println!("{}", s); // コンパイルエラー!
}
関数に String を渡すと所有権ごと渡してしまうため、呼び出し元では使えなくなります。これを解決するのが借用です。
借用(Borrowing):参照を使う
所有権を渡さずに値を使いたいときは、**参照(Reference)**を使います。これを「借用」と呼びます。
fn print_string(s: &String) { // & で参照を受け取る
println!("{}", s);
} // s の参照は消えるが、元の値は解放されない
fn main() {
let s = String::from("world");
print_string(&s); // & で参照を渡す
println!("{}", s); // まだ使える!
}
&String は「String への不変参照」です。借用しているだけなので、所有権は元の変数に残ります。
可変参照(&mut)
値を変更したい場合は、可変参照を使います。
fn add_exclamation(s: &mut String) {
s.push_str("!");
}
fn main() {
let mut s = String::from("hello");
add_exclamation(&mut s);
println!("{}", s); // "hello!"
}
ただし、可変参照には重要な制約があります。
fn main() {
let mut s = String::from("hello");
let r1 = &mut s;
// let r2 = &mut s; // コンパイルエラー!同時に可変参照は1つだけ
}
同じスコープで可変参照は1つしか持てません。 これにより、データ競合(data race)をコンパイル時に防いでいます。
不変参照と可変参照の共存もNG
fn main() {
let mut s = String::from("hello");
let r1 = &s; // 不変参照 OK
let r2 = &s; // 不変参照 OK(複数可)
// let r3 = &mut s; // コンパイルエラー!不変参照が存在する間は可変参照不可
println!("{} {}", r1, r2);
}
不変参照が生きている間は、可変参照を作ることができません。「読み書きが混在する危険な状態」をコンパイラが未然に防いでくれるわけです。
Copy型:ムーブが起きないケース
整数や真偽値など、スタック上に固定サイズで保存できる型は Copy トレイトを実装しており、ムーブではなくコピーが起きます。
fn main() {
let x = 5;
let y = x; // コピーが起きる(x はまだ使える)
println!("x = {}, y = {}", x, y); // OK
}
Copy 型の例:i32, f64, bool, char、タプル(要素がすべてCopy型の場合)
まとめ
Rustの所有権・借用の仕組みを整理しましょう。
| 概念 | 特徴 |
|---|---|
| 所有権 | 値を持てる変数は1つだけ。スコープを抜けると自動解放 |
| ムーブ | 所有権を別の変数や関数に移動。元の変数は使えなくなる |
不変参照 &T | 所有権を渡さず読み取り。複数同時に持てる |
可変参照 &mut T | 所有権を渡さず書き換え可能。同時に1つのみ |
| Copy型 | スタック上の小さな値は自動コピーされムーブしない |
最初は難しく感じるRustの所有権ですが、**「コンパイラが教えてくれるメモリ安全のガイド」**だと思えば、エラーメッセージが味方に見えてきます。エラーが出たら焦らず、どの変数が所有権を持っているかを追ってみましょう。