Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

所有権(Ownership)

Rust最重要の概念です。これを理解すればRustの大部分が理解できます。

所有権とは

Rustでは、すべての値に「所有者」(owner)がいます。所有者はその値に対する責任を持ちます。

所有権の3つのルール

┌──────────────────────────────────────────────────┐
│             所有権の3つのルール                    │
├──────────────────────────────────────────────────┤
│ 1. Rustの各値は「所有者」と呼ばれる変数を持つ        │
│ 2. 所有者は同時に1つだけ存在できる                  │
│ 3. 所有者がスコープを抜けると、値は破棄される         │
└──────────────────────────────────────────────────┘

この3つのルールがRustのメモリ安全性を支えています。

ルール1: 各値には所有者がいる

fn main() {
    let s = String::from("hello");  // sが"hello"の所有者
    println!("{}", s);
}

s"hello"という文字列の所有者です。

ルール2: 所有者は同時に1つだけ

fn main() {
    let s1 = String::from("hello");
    let s2 = s1;  // 所有権がs1からs2に移動(ムーブ)

    // println!("{}", s1);  // エラー!s1はもう所有者ではない
    println!("{}", s2);     // OK
}

ムーブの図解

s1 = String::from("hello");

スタック                     ヒープ
┌─────────────┐            ┌───────────┐
│ s1          │            │           │
│ ptr ─────────────────────→│ "hello"   │
│ len: 5      │            │           │
│ cap: 5      │            └───────────┘
└─────────────┘

s2 = s1;  // ムーブ後

┌─────────────┐            ┌───────────┐
│ s1 (無効)   │            │           │
│ ptr ────X   │            │ "hello"   │
│ len: 5      │            │           │
│ cap: 5      │            └───────────┘
└─────────────┘                  ↑
┌─────────────┐                  │
│ s2          │                  │
│ ptr ─────────────────────────────
│ len: 5      │
│ cap: 5      │
└─────────────┘

ルール3: スコープを抜けると値は破棄

fn main() {
    {
        let s = String::from("hello");
        println!("{}", s);
    }  // ← ここでsがスコープを抜ける。メモリが解放される。

    // println!("{}", s);  // エラー!sは存在しない
}

なぜこれが重要なのか

C言語などでは、メモリの解放を忘れるとメモリリークが起きます。 逆に、同じメモリを2回解放すると二重解放バグが起きます。

Rustは所有権ルールにより、これらをコンパイル時に防ぎます

コピーとムーブ

コピーされる型(Copy型)

スタックに収まる小さな型は、代入時にコピーされます。

fn main() {
    let x = 5;
    let y = x;  // コピー

    println!("x = {}, y = {}", x, y);  // 両方使える
}

Copy型の例: i32, f64, bool, char, タプル(全要素がCopyの場合)

ムーブされる型

ヒープを使う型は、代入時にムーブ(所有権の移動)されます。

fn main() {
    let s1 = String::from("hello");
    let s2 = s1;  // ムーブ

    // println!("{}", s1);  // エラー!
    println!("{}", s2);
}

ムーブされる型の例: String, Vec<T>, Box<T>

clone: 明示的なコピー

ヒープのデータもコピーしたい場合は、cloneを使います。

fn main() {
    let s1 = String::from("hello");
    let s2 = s1.clone();  // ヒープのデータもコピー

    println!("s1 = {}, s2 = {}", s1, s2);  // 両方使える
}
clone後のメモリ:

スタック                     ヒープ
┌─────────────┐            ┌───────────┐
│ s1          │            │ "hello"   │ ← s1用
│ ptr ─────────────────────→│           │
└─────────────┘            └───────────┘
┌─────────────┐            ┌───────────┐
│ s2          │            │ "hello"   │ ← s2用(別の場所)
│ ptr ─────────────────────→│           │
└─────────────┘            └───────────┘

注意: cloneはヒープのデータをコピーするので、大きなデータでは遅くなります。

関数と所有権

関数に渡すとムーブする

fn main() {
    let s = String::from("hello");
    takes_ownership(s);  // sの所有権が関数に移動

    // println!("{}", s);  // エラー!sは無効
}

fn takes_ownership(some_string: String) {
    println!("{}", some_string);
}  // some_stringがスコープを抜けてメモリ解放

関数から返すと所有権が移動

fn main() {
    let s1 = gives_ownership();  // 所有権をもらう
    println!("{}", s1);

    let s2 = String::from("hello");
    let s3 = takes_and_gives_back(s2);  // s2を渡して、新しい所有権をもらう

    // println!("{}", s2);  // エラー!
    println!("{}", s3);     // OK
}

fn gives_ownership() -> String {
    String::from("hello")
}

fn takes_and_gives_back(s: String) -> String {
    s
}

所有権のまとめ図

┌────────────────────────────────────────────────────────┐
│                    所有権の移動パターン                   │
├────────────────────────────────────────────────────────┤
│                                                        │
│  変数代入: let s2 = s1;       → s1からs2にムーブ         │
│                                                        │
│  関数呼出: func(s);           → sから引数にムーブ         │
│                                                        │
│  関数戻値: let s = func();    → 関数からsにムーブ         │
│                                                        │
│  clone:   let s2 = s1.clone(); → コピー(両方使える)     │
│                                                        │
└────────────────────────────────────────────────────────┘

よくあるエラーと対処

エラー: use of moved value

fn main() {
    let s = String::from("hello");
    let s2 = s;
    println!("{}", s);  // error: use of moved value: `s`
}

対処法:

  1. cloneを使う: let s2 = s.clone();
  2. 参照を使う(次のドキュメントで学習): let s2 = &s;

まとめ

ルール内容
ルール1各値には所有者がいる
ルール2所有者は同時に1つだけ
ルール3スコープを抜けると値は破棄
操作Copy型ムーブ型
代入コピームーブ
関数に渡すコピームーブ
両方使いたいそのままclone

確認テスト

Q1. 所有権のルールとして正しいものはどれですか?

Q2. 以下のうち、Copy型(代入時にコピーされる型)はどれですか?

Q3. 以下のコードはコンパイルできますか?
let s1 = String::from("hello"); let s2 = s1; println!("{}", s1);

Q4. 関数にStringを渡した後も元の変数を使いたい場合、最も効率的な方法はどれですか?

Q5. clone()メソッドについて正しい説明はどれですか?


次のドキュメント: 03_references_borrowing.md