404 Not Found

404 Not Found


nginx

Rustの参照と借用:&Tおよび&mut Tの完全ガイド

引用は図書館の貸出カードのようなものです。借りる(読み取り専用引用)ことも、書き換えるための独占的な許可を求める(編集可能な引用)こともできますが、両方を同時に行うことはできません。

Rustの参照メカニズムにより、所有権を移転することなくデータにアクセスすることができます。これを「借用」と呼びます。


1. 学習内容


2. 概念図

100%
flowchart LR
    subgraph "Transfer of Ownership"
        A1["Data String"] -->|"let s2 = s1<br>move"| B1["New Owner s2"]
        B1 -->|"s1 Failure"| C1["Compilation Error<br>println!(s1)"]
    end
    subgraph "Citation, Borrowing"
        A2["Data String"] -->|"let r = &s<br>borrow"| B2["Quote r"]
        B2 -->|"r Return after use<br>s Still valid"| C2["✅ Secure Access"]
    end

3. ある図書館の物語

(1) 悲劇:その本はボロボロに破かれてしまった

トムの図書館には『Programming in Rust』という人気の本があり、この本は1回しか借りることができません:

「もし、何人もの人が同じ本を同時に読んでも、その本をボロボロにされずに済むなら……」

(2) Rustの参照に関する解決策

RUST
fn main() {
    let book = String::from("Rust Programming");

    // Multiple immutable references: Multiple people at the same time "read-only" check out
    let reader1 = &book;   // Reader 1 checks out (read-only)
    let reader2 = &book;   // Reader 2 checks out (read-only)
    println!("Readers 1 See: {}", reader1);
    println!("Readers 2 See: {}", reader2);
    // Both references are valid at the same time -- because everyone has read-only access

    let mut notebook = String::from("Notebook");

    // Mutable References: write-only permission
    let writer = &mut notebook;  // Only one person can make changes
    writer.push_str("-- I wrote some notes");
    println!("Rewriter: {}", writer);
    // writer ends scope here, only then can others borrow it again.
}

引用ルールは図書館の規則のようなものです。複数の人が同時に読むことができる(複数 &T)か、1人が単独で編集できる(1人 &mut T)かのいずれかですが、両方が同時に起こることはありません。


4. 貸出規則

100%
graph TB
    A[Borrowing Rules] --> B[You can only choose one of them.]
    B --> C[Multiple immutable references &T]
    B --> D[Or a mutable reference &mut T]
    B --> E[Cannot coexist]
    A --> F[References must always be valid.]
    F --> G[There must be no dangling references.]
規則 説明 違反時の措置
複数の &T または単一の &mut T 不変参照と可変参照は共存できない コンパイルエラー
参照は有効でなければならない 参照は、それが指すオブジェクトの存続期間を超えてはならない コンパイルエラー
参照が不変である場合、元の値も変更することはできません &T が存在する場合、&mut T を通じて元の値を変更することはできません コンパイルエラー
NLLの範囲 &mut T の範囲は、最後の使用後に終了する コンパイラによる最適化

(2) 参照型の比較

引用タイプ 構文 権限 最大許容数 代表的な用途
不変参照 &T 読み取り専用 複数 関数パラメータへの読み取り専用アクセス
変数参照 &mut T 読み取り/書き込み 1 関数の引数がデータを変更する
ファットポインタ(スライス) &[T] 読み取り専用 複数 配列/文字列のスライス

(3) 借入シナリオのクイックリファレンス

シナリオ 推奨されるアプローチ
読み取り専用関数へのアクセス &T fn len(s: &String) -> usize
データを変更する関数 &mut T fn push(s: &mut String, ch: char)
関数のデータ使用方法 T (値渡し) fn consume(s: String)
関数の戻り値 T (戻り値) fn create() -> String
複数の関数が読み取り専用アクセスを共有する &T 複数の関数が同じ &T を受け取る
交互の変更 &mut T(時分割多重化) NLL により &mut T の交互生成が可能

5. 引用例

(1) ▶ サンプル:不変参照 — 読み取り専用アクセス (難易度 ⭐⭐)

RUST
// ============================================
// Immutable References &T: Multiple readers can borrow items at the same time
// ============================================

fn calculate_length(s: &String) -> usize {
    // s is a String reference, no ownership
    s.len()
}  // s goes out of scope, but since it's a reference, it will not destroy the String

fn main() {
    let s = String::from("Hello, Rust!");

    let len = calculate_length(&s);  // Borrow s, no transfer of ownership
    println!("'{}' The length of is: {}", s, len);  // ✅ s Still available

    // Multiple immutable references can coexist
    let r1 = &s;
    let r2 = &s;
    println!("r1: {}, r2: {}", r1, r2);  // ✅ Read simultaneously
}

出力:

TEXT
'Hello, Rust!' The length of is: 12
r1: Hello, Rust!, r2: Hello, Rust!

&ss への参照を作成します。これを関数に渡しても、所有権は移転されません。関数が返った後、その参照は無効になりますが、s は依然として存在します。これが「借用」と呼ばれるもので、使用した後、元の状態に戻すという仕組みです。


(2) ▶ サンプル:可変参照 — 排他的な変更権限 (難易度 ⭐⭐)

RUST
// ============================================
// Mutable References &mut T: Only one writer at a time
// ============================================

fn main() {
    let mut s = String::from("Hello");

    // Create a mutable reference
    let r = &mut s;
    r.push_str(", world!");
    println!("Modifying Through a Variable Reference: {}", r);
    // r is used here for the last time, NLL ends

    // After the mutable reference ends, you can create a new mutable reference.
    let r2 = &mut s;
    r2.push_str("!!");
    println!("Another mutable reference: {}", r2);

    // --- Examples of Errors (Uncomment to view compilation errors) ---
    // let mut s2 = String::from("test");
    // let ref1 = &mut s2;
    // let ref2 = &mut s2;  // ❌ There cannot be two mutable references at the same time.
    // println!("{}, {}", ref1, ref2);
}

出力:

TEXT
Modifying Through a Variable Reference: Hello, world!
Another mutable reference: Hello, world!!!

&mut は可変であり、参照先の値を変更することができます。ただし、Rust では 同時に存在できる可変参照は 1 つだけ というルールが強制されます。これによりデータ競合が防止されます。ロックやアトミック操作は不要であり、この問題はコンパイル時に完全に解決されます。


(3) ▶ サンプル:不変参照と可変参照は共存できない(難易度 ⭐⭐⭐)

RUST
// ============================================
// Demonstration of Conflicts Between Immutable and Mutable References
// ============================================

fn main() {
    let mut data = String::from("Key Data");

    let r1 = &data;       // ✅ Immutable References 1
    let r2 = &data;       // ✅ Immutable References 2
    println!("r1: {}, r2: {}", r1, r2);
    // r1 and r2 are used here for the last time

    let r3 = &mut data;   // ✅ At this point, you can create a mutable reference.
    r3.push_str("--Modified");
    println!("r3: {}", r3);

    // --- Examples of Errors: A mutable reference cannot be created while an immutable reference still exists. ---
    // let mut s = String::from("Demo");
    // let r_a = &s;        // Immutable References
    // let r_b = &mut s;    // ❌ Compilation Error: immutable reference already exists
    // println!("{}, {}", r_a, r_b);
}

出力:

TEXT
r1: Key Data, r2: Key Data
r3: Key Data--Modified

要点:不変参照 r1 および r2 は、最後の使用(println!)後にスコープ外となり、その時点で初めて可変参照 r3 が作成される。これがNLL(非レキシカルライフタイム)です。コンパイラは、ブロックの終了を待つのではなく、参照がいつ使用されなくなったかをインテリジェントに判断します。


(4) ▶ サンプル:参照の行き詰まりを防ぐ(難易度 ⭐⭐⭐)

RUST
// ============================================
// How Do Compilers Prevent Dangling References?
// ============================================

// Examples of Errors: Return a reference to a local variable
// fn dangle() -> &String {
//     let s = String::from("hello");
//     &s  // ❌ Compilation Error: s is destroyed at the end of the function, the reference would be dangling
// }

// The Correct Approach: Return String directly (transfer of ownership)
fn no_dangle() -> String {
    let s = String::from("hello");
    s  // Return String directly, ownership is transferred to the caller
}

fn main() {
    let s = no_dangle();
    println!("Safe Return Values: {}", s);

    // The Correct Way to Reference Local Variables: Use within the scope
    let local = String::from("Local Data");
    let r = &local;          // Quote
    println!("Referencing Local Variables: {}", r);
    // r ends here, local is still valid
}  // local is destroyed here (later than r), safe

出力:

TEXT
Safe Return Values: hello
Referencing Local Variables: Local Data

ダングリング参照とは、参照が指すメモリ領域の割り当てが解除された状態のことです。Rustコンパイラはコンパイル時にこの状況を検出できます。つまり、参照が作成された後に、その参照が指すオブジェクトが破棄された場合、コンパイラはエラーを報告します。これにより、「ダングリングポインタ」のようなバグを完全に排除することができます。


(5) ▶ サンプル:題 5:総合演習—銀行口座の取引(難易度 ⭐⭐⭐)

RUST
// ============================================
// Comprehensive Example: Applications of References in Real-Life Scenarios
// ============================================

struct Account {
    owner: String,
    balance: f64,
}

fn check_balance(account: &Account) -> String {
    format!("{} balance: {:.2} yuan", account.owner, account.balance)
}

fn deposit(account: &mut Account, amount: f64) {
    if amount <= 0.0 {
        println!("The deposit amount must be greater than 0");
        return;
    }
    account.balance += amount;
    println!("Deposit {:.2} Yuan Chenggong", amount);
}

fn transfer(from: &mut Account, to: &mut Account, amount: f64) -> bool {
    if amount <= 0.0 || from.balance < amount {
        println!("Transfer Failed: Insufficient balance or invalid amount");
        return false;
    }
    from.balance -= amount;
    to.balance += amount;
    println!("Transfer {:.2} yuan: {} -> {}", amount, from.owner, to.owner);
    true
}

fn show_accounts(accounts: &[Account]) {
    println!("--- Account List ---");
    for (i, acc) in accounts.iter().enumerate() {
        println!("{}. {} balance: {:.2} yuan", i + 1, acc.owner, acc.balance);
    }
}

fn main() {
    let mut alice = Account { owner: String::from("Alice"), balance: 1000.0 };
    let mut bob = Account { owner: String::from("Bob"), balance: 500.0 };

    println!("{}", check_balance(&alice));
    println!("{}", check_balance(&bob));

    deposit(&mut alice, 200.0);
    println!("{}", check_balance(&alice));

    transfer(&mut alice, &mut bob, 300.0)?;

    let accounts = [&alice, &bob];
    show_accounts(&accounts);

    let total: f64 = accounts.iter().map(|a| a.balance).sum();
    println!("Total Deposits: {:.2} yuan", total);
}

出力:

TEXT
Alice balance: 1000.00 yuan
Bob balance: 500.00 yuan
Deposit 200.00 Yuan Successful
Alice balance: 1200.00 yuan
Transfer 300.00 yuan: Alice -> Bob
--- Account List ---
1. Alice balance: 900.00 yuan
2. Bob balance: 800.00 yuan
Total Deposits: 1700.00 yuan

この例では、3つの参照型、すなわち &T(読み取り専用クエリ)、&mut T(変更操作)、および &[T](読み取り専用探索)の実用的な活用例を示しています。transfer では、2つの &mut T 参照を同時に必要としますが、これらは異なるデータを指しているため、Rust では安全です。


❓ よくある質問

Q 参照とポインタの違いは何ですか?
A 参照とは、有効であることが保証されたポインタのことです。
Q &T と &mut T は共存できますか?
A いいえ。
Q NLL(非レキシカルライフタイム)とは何ですか?
A NLL では、参照はブロックの終了を待つのではなく、最後に使用された後に失効します。
Q 所有権渡しではなく、参照を使うべき場合はいつですか?
A データを単に「確認」するだけでよい場合は参照を使い、データを長期的に「所有」したり「保持」したりする必要がある場合は所有権渡しを使います。
Q 構造体(struct)に参照を格納することはできますか?
A はい、できますが、ライフタイムパラメータを指定する必要があります。

📖 まとめ


📝 練習問題

  1. 難易度 ⭐:渡された文字列を出力する関数 fn print_message(msg: &String) を作成してください。main 内で String を作成し、この関数を呼び出して、呼び出し後も元の変数にアクセスできることを確認してください。
  2. 難易度 ⭐⭐mut String を作成するプログラムを作成してください。まず、2つの不変参照を作成して出力し、次に可変参照を作成してその内容を変更し、変更後の結果を出力してください。
  3. 難易度 ⭐⭐⭐: 参照(例:fn get_ref() -> &String)を返す関数を作成し、コンパイラのエラーメッセージを確認してみてください。次に、String の所有権を返すように関数を修正し、Rust がローカル変数への参照の返却を禁止している理由を理解してください。
Web-Tutorial.com

Web-Tutorial 技術チーム

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

100%