Rustの参照と借用:&Tおよび&mut Tの完全ガイド
引用は図書館の貸出カードのようなものです。借りる(読み取り専用引用)ことも、書き換えるための独占的な許可を求める(編集可能な引用)こともできますが、両方を同時に行うことはできません。
Rustの参照メカニズムにより、所有権を移転することなくデータにアクセスすることができます。これを「借用」と呼びます。
1. 学習内容
&Tを使用して、不変の参照(読み取り専用の借用)を作成します&mut Tを使用して、可変参照(読み書き可能な借用)を作成する- 借用ルール:変更可能な参照を1つ、または変更不可能な参照を複数(両方を同時に使用することはできない)
- 参照の範囲とNLL(非語彙的存続期間)
- 未解決の参照とコンパイラの安全対策
- 関数パラメータにおける参照の使い方
2. 概念図
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回しか借りることができません:
- アリスがこの本を借りたため、所有権はアリスに移った
- ボブはその本を読みたかったが、図書館にはもうその本がなかった。その本はたった一人の人の所有物だったのだ。
- 図書館は、もう1冊購入しなければならないでしょう。
「もし、何人もの人が同じ本を同時に読んでも、その本をボロボロにされずに済むなら……」
(2) 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. 貸出規則
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) ▶ サンプル:不変参照 — 読み取り専用アクセス (難易度 ⭐⭐)
// ============================================
// 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
}
出力:
'Hello, Rust!' The length of is: 12
r1: Hello, Rust!, r2: Hello, Rust!
&sはsへの参照を作成します。これを関数に渡しても、所有権は移転されません。関数が返った後、その参照は無効になりますが、sは依然として存在します。これが「借用」と呼ばれるもので、使用した後、元の状態に戻すという仕組みです。
(2) ▶ サンプル:可変参照 — 排他的な変更権限 (難易度 ⭐⭐)
// ============================================
// 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);
}
出力:
Modifying Through a Variable Reference: Hello, world!
Another mutable reference: Hello, world!!!
&mutは可変であり、参照先の値を変更することができます。ただし、Rust では 同時に存在できる可変参照は 1 つだけ というルールが強制されます。これによりデータ競合が防止されます。ロックやアトミック操作は不要であり、この問題はコンパイル時に完全に解決されます。
(3) ▶ サンプル:不変参照と可変参照は共存できない(難易度 ⭐⭐⭐)
// ============================================
// 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);
}
出力:
r1: Key Data, r2: Key Data
r3: Key Data--Modified
要点:不変参照
r1およびr2は、最後の使用(println!)後にスコープ外となり、その時点で初めて可変参照r3が作成される。これがNLL(非レキシカルライフタイム)です。コンパイラは、ブロックの終了を待つのではなく、参照がいつ使用されなくなったかをインテリジェントに判断します。
(4) ▶ サンプル:参照の行き詰まりを防ぐ(難易度 ⭐⭐⭐)
// ============================================
// 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
出力:
Safe Return Values: hello
Referencing Local Variables: Local Data
ダングリング参照とは、参照が指すメモリ領域の割り当てが解除された状態のことです。Rustコンパイラはコンパイル時にこの状況を検出できます。つまり、参照が作成された後に、その参照が指すオブジェクトが破棄された場合、コンパイラはエラーを報告します。これにより、「ダングリングポインタ」のようなバグを完全に排除することができます。
(5) ▶ サンプル:題 5:総合演習—銀行口座の取引(難易度 ⭐⭐⭐)
// ============================================
// 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);
}
出力:
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 では安全です。
❓ よくある質問
📖 まとめ
- 参照 を使用すると、所有権を移転することなく、つまりデータを「借りる」ことで、データにアクセスすることができます。
- 不変参照
&T: 複数のインスタンスが同時に存在可能。読み取り専用。 - 変数参照
&mut T:一度に1つだけ;読み取り/書き込み - 借用ルール:複数の
&Tまたは単一の&mut Tを同時に使用することはできません - 参照は常に有効でなければなりません。コンパイラはダングリング参照を防止します。
- NLL を使用すると、参照はブロックの終了まで待つのではなく、最後の使用後に終了します
📝 練習問題
- 難易度 ⭐:渡された文字列を出力する関数
fn print_message(msg: &String)を作成してください。main内でStringを作成し、この関数を呼び出して、呼び出し後も元の変数にアクセスできることを確認してください。 - 難易度 ⭐⭐:
mut Stringを作成するプログラムを作成してください。まず、2つの不変参照を作成して出力し、次に可変参照を作成してその内容を変更し、変更後の結果を出力してください。 - 難易度 ⭐⭐⭐: 参照(例:
fn get_ref() -> &String)を返す関数を作成し、コンパイラのエラーメッセージを確認してみてください。次に、Stringの所有権を返すように関数を修正し、Rust がローカル変数への参照の返却を禁止している理由を理解してください。



