404 Not Found

404 Not Found


nginx

Rustの所有権:メモリ安全性の3つの基本原則

所有権はRustの最も際立った特徴であり、これによりRustはガベージコレクタを必要とせずにメモリの安全性を保証することができる。

「所有権」こそが、Rustと他の言語との最も根本的な違いです。これを理解すれば、Rustの設計の本質が理解できるでしょう。


1. 学習内容


2. 概念図

100%
flowchart LR
    A["Value Creation<br>let s = String::from(...)"] --> B["Owner Binding<br>s Has this value"]
    B --> C["Transfer of Ownership move<br>let s2 = s; s Failure"]
    C --> D["Out of scope drop<br>} Automatic Memory Release"]
    D --> E["Memory Security<br>No Double Release,Non-swinging pointer"]

3. ある図書館の物語

(1) 苦悩:その本は2人に同時に借りられていた

トムは小さな図書館を運営しています。彼はよくある問題に直面しました:

「もし、一冊の本を一度にたった一人だけが借りられるとしたら……」

(2) Rustの所有権に関するルール

RUST
fn main() {
    let book = String::from("Rust Programming");  // book is the owner of this book
    // let book2 = book;                   // ❌ If you write it this way,book Ownership was transferred to book2
    // println!("{}", book);               // ❌ book Can no longer be used

    // ✅ The Correct Approach:Only one person can hold ownership at a time.
    println!("{} Belongs to Tom The Library", book);
}  // book Automatically destroyed here——No manual intervention required free

Rustの所有権のルールは、図書館の「一度に1人まで」という利用規則のようなものです。どの値も、任意の時点で所有者は正確に1人だけです。所有者がスコープ外に出ると、その値は自動的に破棄されます。


4. 所有権の3つの原則

100%
graph TB
    A[The Three Principles of Ownership] --> B[Principles1: Each value has exactly one owner.]
    A --> C[Principles2: It is destroyed as soon as it leaves its scope.]
    A --> D[Principles3: Ownership can be transferred(move)]
    B --> E[let s = String::from("hi")]
    C --> F[} Automatically call at the end drop]
    D --> G[let s2 = s;  // s No longer valid]

(1) 原則に関する詳細な説明

原則 説明 類例
単一所有者 どの時点においても、各値は1人のみが「所有」する 図書館の本は、一度に1人しか借りることができない
スコープ終了 = 破棄 変数がスコープ外になったときに自動的に呼び出される drop 返却期限が過ぎると、本は自動的に返却される
所有権の移転(移動) 代入やパラメータの受け渡し時に所有権が移転し、元の変数は無効になる 本が人から人へと手渡される

5. スタックとヒープ

所有権を理解するには、まずスタックとヒープの違いを理解する必要があります:

ディメンション スタック ヒープ
割り当て速度 非常に高速(push/pop) やや遅い(空きメモリの検索が必要)
データ格納 コンパイル時にサイズが既知のデータ サイズが不明または動的に変化するデータ
代表的なタイプ i32,bool,f64,Array String,Vec,Box
メモリ管理 自動(関数呼び出しスタックフレーム) 手動による管理または所有権ベースの管理が必要

(3) コピー・クローン・移動の比較

演算 構文 元の変数は有効か? パフォーマンスのオーバーヘッド 適用可能な型
コピー let b = a; 有効 非常に低い(スタックのビット単位のコピー) i32f64boolchar、タプル(Copy型を含む)
クローン let b = a.clone(); 有効 高 (スタックメモリの割り当て) String, Vec<T>, Box<T>
移動 let b = a; 有効期限 0(ポインタのみをコピーする) String, Vec<T>, Box<T>

型が Copy を実装している場合、代入によって自動コピーが行われます。一方、Copy を実装していない場合、代入によって自動移動が行われます。元の変数を保持する必要がある場合は、明示的な深部コピーを行うために .clone() を使用してください。

RUST
fn main() {
    // Data on the Stack:Fixed size,Copy Semantics
    let x: i32 = 5;        // Allocation on the Stack 4 Byte
    let y = x;              // Make a copy, x and y are both valid.
    println!("x={}, y={}", x, y);  // ✅ All are acceptable

    // Load the data:Varies in size,move Semantics
    let s1 = String::from("hello");  // s1 On the stack(ptr/len/cap),The actual data is on the heap.
    let s2 = s1;                     // ❌ Ownership from s1 Transfer to s2
    // println!("{}", s1);            // ❌ Compilation Error:s1 Has been moved
    println!("{}", s2);              // ✅ s2 It's the new owner.
}

6. 所有権の例

(1) ▶ サンプル:「move」のセマンティクス――所有権の移転(難易度 ⭐⭐)

RUST
// ============================================
// Demonstration of Ownership Transfer:Occurs during assignment move
// ============================================

fn main() {
    let s1 = String::from("Rust");
    let s2 = s1;  // s1 Ownership was transferred to s2

    // println!("s1: {}", s1);  // ❌ Compilation Error!s1 Has been moved
    println!("s2: {}", s2);     // ✅ s2 I am the owner now

    // Integer types are Copy, So it won't move
    let a = 42;
    let b = a;                 // a The value is copied to b
    println!("a: {}, b: {}", a, b);  // ✅ Both are effective.
}

出力:

TEXT
s2: Rust
a: 42, b: 42

整数などのスカラー型は Copy トレイトを実装しているため、代入時には移動ではなくコピーが行われます。一方、StringCopy を実装していないため、代入時には移動が行われます。移動後、元の変数は無効になります。これは、「ダブルフリー」を防ぐための Rust の重要な設計上の特徴です。


(2) ▶ サンプル:関数呼び出しにおける所有権の引き渡し (難易度 ⭐⭐)

RUST
// ============================================
// Transfer of Ownership in Parameter Passing and Return Values
// ============================================

fn take_ownership(s: String) {
    println!("Acquire ownership: {}", s);
}  // s Destroyed here(drop)

fn give_ownership() -> String {
    let s = String::from("Newly created string");
    s  // Ownership is returned to the caller
}

fn main() {
    let s1 = String::from("hello");

    take_ownership(s1);  // s1 Ownership was transferred to the function
    // println!("{}", s1);  // ❌ s1 Expired

    let s2 = give_ownership();  // Acquiring Ownership from a Function
    println!("s2: {}", s2);     // ✅ s2 Ownership

    // Send it in and then send it back
    let s3 = String::from("Passing back and forth");
    let s3_back = takes_and_returns(s3);
    // println!("{}", s3);  // ❌ s3 Has been moved
    println!("s3_back: {}", s3_back);  // ✅
}

fn takes_and_returns(s: String) -> String {
    println!("Inside a function: {}", s);
    s  // Return ownership to the caller
}

出力:

TEXT
Acquire ownership: hello
s2: Newly created string
Inside a function: Passing back and forth
s3_back: Passing back and forth

関数が呼び出されると、渡された引数の所有権は関数に移ります。関数が値を返すと、所有権は呼び出し元に返されます。これが「関数間で所有権が流れる」という意味であり、「鍵を持っている者がドアを開けることができる」という原則と同じです。


(3) ▶ サンプル:「コピー」と「クローン」の比較(難易度:⭐⭐⭐)

RUST
// ============================================
// Copy(Auto-Copy) vs Clone(Explicit Cloning)
// ============================================

fn main() {
    // --- Copy Type:Automatic Copy on Assignment ---
    let num1 = 100;
    let num2 = num1;         // Auto-Copy,num1 Still valid
    println!("Copy: num1={}, num2={}", num1, num2);  // ✅

    // --- Clone Type:Must be explicitly called .clone() ---
    let s1 = String::from("Cloning required");
    let s2 = s1.clone();     // Explicit Cloning,s1 Still valid
    println!("Clone: s1={}, s2={}", s1, s2);  // ✅

    // --- Neither Clone Nor Copy: only move ---
    let v1 = vec![1, 2, 3];
    let v2 = v1;  // move!v1 Failure
    // println!("{:?}", v1);  // ❌
    println!("v2: {:?}", v2);  // ✅
}

出力:

TEXT
Copy: num1=100, num2=100
Clone: s1=Cloning required, s2=Cloning required
v2: [1, 2, 3]

コピー:代入時に自動的にビット単位のコピーを実行します。元の変数は有効なままです(スタック上のデータに適用されます)。クローン.clone() メソッドを明示的に呼び出す必要があります。ヒープ上のデータに適用されます。クローンは処理コストが高いため(ヒープメモリの割り当てが必要となる)、Rust では「クローンを行う場合は明示的に選択しなければならない」ように設計されています。


(4) ▶ サンプル:関数内での所有権の取得と返却に関するパターン (難易度 ⭐⭐⭐)

RUST
// ============================================
// Common Patterns:Send it in and then send it back(Get+Return)
// ============================================

struct User {
    name: String,
    score: i32,
}

fn boost_score(mut user: User, bonus: i32) -> User {
    user.score += bonus;
    user
}

fn format_user(user: &User) -> String {
    format!("{}: {} pts", user.name, user.score)
}

fn main() {
    let alice = User {
        name: String::from("Alice"),
        score: 80,
    };

    println!("Before the upgrade: {}", format_user(&alice));

    let alice = boost_score(alice, 15);
    println!("After the upgrade: {}", format_user(&alice));

    let alice = boost_score(alice, 10);
    println!("Upgrade Again: {}", format_user(&alice));

    let mut bob = User {
        name: String::from("Bob"),
        score: 60,
    };

    let bonus = 20;
    bob.score += bonus;
    println!("Bob After the bonus points are added: {}", format_user(&bob));

    let names = vec![alice.name.clone(), bob.name.clone()];
    println!("All users: {:?}", names);

    println!("Alice Final Score: {}", alice.score);
    println!("Bob Final Score: {}", bob.score);
}

出力:

TEXT
Before the upgrade: Alice: 80 pts
After the upgrade: Alice: 95 pts
Upgrade Again: Alice: 105 pts
Bob After the bonus points are added: Bob: 80 pts
All users: ["Alice", "Bob"]
Alice Final Score: 105
Bob Final Score: 80

構造体の所有権の譲渡は、基本型の場合と同様の方法で行われます。boost_score 所有権はパラメータを通じて取得され、変更後に返されます。これが「取得・返却」パターンです。参照を使用することで(&User)、所有権を取得することなく読み取り専用のアクセスが可能になります。


(5) ▶ サンプル:所有関係と集合の相互作用(難易度 ⭐⭐⭐)

RUST
// ============================================
// Vec,String Common Interaction Patterns with Ownership
// ============================================

fn take_first_word(words: &Vec<String>) -> Option<&str> {
    words.first().map(|s| s.as_str())
}

fn remove_and_return(vec: &mut Vec<i32>, index: usize) -> Option<i32> {
    if index < vec.len() {
        Some(vec.remove(index))
    } else {
        None
    }
}

fn main() {
    let mut words = vec![
        String::from("hello"),
        String::from("rust"),
        String::from("world"),
    ];
    println!("Original vector: {:?}", words);

    if let Some(first) = take_first_word(&words) {
        println!("The First Word: {}", first);
    }

    let removed = remove_and_return(&mut words, 1);
    println!("Remove Index 1: {:?}, Remaining: {:?}", removed, words);

    let mut numbers = vec![10, 20, 30, 40, 50];
    while let Some(val) = numbers.pop() {
        println!("Pop up: {}", val);
    }
    println!("The vector is empty: {:?}", numbers);

    let s1 = String::from("foo");
    let s2 = String::from("bar");
    let combined = s1 + &s2;
    println!("Stitching Results: {}", combined);

    let data = vec![String::from("a"), String::from("b")];
    for word in &data {
        println!("Borrow: {}", word);
    }
    println!("The vector is still available: {:?}", data);

    let data2 = vec![1, 2, 3];
    for num in data2 {
        println!("Consumption: {}", num);
    }
}

出力:

TEXT
Original vector: ["hello", "rust", "world"]
The First Word: hello
Remove Index 1: Some(20), Remaining: ["hello", "world"]
Pop up: 50
Pop up: 40
Pop up: 30
Pop up: 20
Pop up: 10
The vector is empty: []
Stitching Results: foobar
Borrow: a
Borrow: b
The vector is still available: ["a", "b"]
Consumption: 1
Consumption: 2
Consumption: 3

for item in &vec トラバーサルを借用する(ベクトルはそのまま利用可能のまま)。for item in vec トラバーサルを消費する(ベクトルは移動される)。+ この演算子は左側の文字列を消費する。pop() Option<T> を返し、最後の要素を消費する。


❓ よくある質問

Q なぜRustでは所有権の仕組みが採用されたのですか?ガベージコレクションを使ったほうが単純ではないでしょうか?
A 所有権の仕組みにより、RustはGCによる一時停止なしにメモリの安全性を保証できます。
Q move の後、元の変数はどうなるのですか?
A 元の変数は、コンパイラによって「無効」とマークされます。
Q どの型がコピー可能ですか?
A すべてのスカラー型(整数、浮動小数点数、ブール値、文字)およびそれらで構成されるタプルや配列です。
Q .clone()move、どちらのパフォーマンスが優れていますか?
A move はオーバーヘッドがゼロですが、clone ではヒープメモリの割り当てが必要になります。
Q 構造体における所有権はどのように扱われますか?
A 構造体全体の所有権に関するルールは、単一の変数の場合と同じです。

📖 まとめ


📝 練習問題

  1. 難易度 ⭐String を作成し、それを別の変数に代入した後、最初の変数を表示しようとするプログラムを作成してください。コンパイラのエラーメッセージを確認してください。
  2. 難易度 ⭐⭐: 文字列の末尾に「 world」を追加して返す関数 fn append_world(s: String) -> String を作成してください。main でこの関数を呼び出し、関数への引数の受け渡しや関数からの戻り値の受け取りにおいて、所有権がどのように扱われるかを確認してください。
  3. 難易度 ⭐⭐⭐: 文字列とその長さを返し、かつその文字列の所有権を返す関数 fn calculate_length(s: String) -> (String, usize) を定義してください。関数の呼び出し後も、元の変数が引き続き使用できることを確認してください。
Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%