第4章 所有権と借用
この章では、Rust最大の特徴である「所有権(Ownership)」と「借用(Borrowing)」について詳しく学びます。Rustのメモリ安全性を支える重要な概念です。C言語などでありがちな、解放済みメモリへのアクセス(ダングリングポインタ)や、複数スレッドによるデータ競合などの問題をコンパイル時に防止できるのがRustの大きな利点です。
所有権の基本
Rustでは、すべての値は「所有者」を持ち、その所有権は変数に紐づきます。所有権は以下の3つのルールで管理されます。
- Rustの各値は、1つの所有者(owner)しか持てない。
- 所有者がスコープから外れると、値は自動的に破棄される(dropされる)
- 所有権は他の変数にムーブ(move)できるが、コピーではない。
fn main() {
let s1 = String::from("Rust"); // s1 が所有者
let s2 = s1; // 所有権が s2 に移動(ムーブ)
// println!("{}", s1); ← エラー!s1 はもう使えない
println!("{}", s2);
}
この例では、String 型はヒープにデータを持つため、ムーブが発生します。所有権が移ると、元の変数は使えなくなります。
一方、i32 などのスカラー型はスタックに格納されるため、コピーとして動作します。
fn main() {
let x = 5;
let y = x; // 所有権ではなくコピー
println!("x = {}, y = {}", x, y); // 両方使える
}
借用の基本
所有権をムーブせずに値を利用したい場合は「借用(Borrowing)」を行います。借用には2種類あります。
不変な借用(読み取り専用)
fn main() {
let s = String::from("Hello");
print_message(&s); // 参照を渡す
println!("元の値: {}", s); // 問題なく使える
}
fn print_message(msg: &String) {
println!("メッセージ: {}", msg);
}
- &T は読み取り専用の参照(不変参照)を意味します。
- 何個でも同時に借用可能です。
可変な借用(書き込み可能)
fn main() {
let mut s = String::from("Hi");
add_message(&mut s); // 可変参照を渡す
println!("変更後: {}", s);
}
fn add_message(msg: &mut String) {
msg.push_str(", Rust!");
}
- &mut T は書き込み可能な参照(可変参照)です。
- 可変参照は同時に1つだけ存在可能。不変参照と同時には使えません。
fn main() {
let mut s = String::from("Rust");
let r1 = &s; // 不変参照OK
let r2 = &s; // 2つ目もOK
println!("{} and {}", r1, r2);
// let r3 = &mut s; // エラー!不変参照がまだ生きている
}
スコープとライフタイム
Rustでは参照の有効範囲(ライフタイム)にも厳密なルールがあります。
fn main() {
let r; // r はまだ初期化されていない
{
let x = 5;
// r = &x; // エラー!x はこのブロックで消える
}
// println!("r: {}", r);
}
このようなミスもコンパイル時に検出され、メモリ安全性が保証されます。
ダングリング参照の禁止
Rustでは、所有権が失われた後の参照(ダングリングポインタ)はコンパイルエラーになります。
- 算術演算子:+, -, *, /, %
- 論理演算子:&&, ||, !
- 比較演算子:==, !=, <, >, <=, >=
// fn dangle() -> &String { // コンパイルエラー!
// let s = String::from("Dangling");
// &s // s はスコープを抜けて消える
// }
練習問題1.
String 型の変数を定義し、それを別の変数にムーブしてみましょう。その後、元の変数を使ってみてエラー内容を確認してください。
練習問題2.
不変参照を2つ同時に作り、それらを関数に渡してみましょう。その後、可変参照に切り替えるとどうなるかを確認してください。
練習問題3.
文字列を受け取って、末尾に「 - Rust入門」と追加する関数を作成し、可変参照を使って操作してみてください。