0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Rustの所有権を10分で理解する

0
Posted at

なぜ所有権が必要なのか

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プロフィール

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?