あっぽログ
← 記事一覧に戻る

Rustの所有権と借用を図解で理解する

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の所有権ですが、**「コンパイラが教えてくれるメモリ安全のガイド」**だと思えば、エラーメッセージが味方に見えてきます。エラーが出たら焦らず、どの変数が所有権を持っているかを追ってみましょう。

← 記事一覧に戻る