第4章 所有権と借用

この章では、Rust最大の特徴である「所有権(Ownership)」と「借用(Borrowing)」について詳しく学びます。Rustのメモリ安全性を支える重要な概念です。C言語などでありがちな、解放済みメモリへのアクセス(ダングリングポインタ)や、複数スレッドによるデータ競合などの問題をコンパイル時に防止できるのがRustの大きな利点です。


所有権の基本

Rustでは、すべての値は「所有者」を持ち、その所有権は変数に紐づきます。所有権は以下の3つのルールで管理されます。

  1. Rustの各値は、1つの所有者(owner)しか持てない。
  2. 所有者がスコープから外れると、値は自動的に破棄される(dropされる)
  3. 所有権は他の変数にムーブ(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入門」と追加する関数を作成し、可変参照を使って操作してみてください。