第5章: 参照と借用
学習目標
- 参照の基本概念を理解する
- 借用のルールを把握する
- 可変参照と不変参照の違いを学ぶ
- ダングリング参照を防ぐ方法を知る
---
5.1 参照とは何か?
5.1.1 所有権の問題を再確認
前章で学んだように、関数に値を渡すと所有権が移動します:
fn main() {
let s = String::from("hello");
let len = calculate_length(s);
// println!("{}", s); // ❌ エラー!sはムーブ済み
println!("長さ: {}", len);
}
fn calculate_length(s: String) -> usize {
s.len()
} // sがドロップされる
問題点:
- 値を使うたびに所有権を返さないといけない
- コードが煩雑になる
5.1.2 参照による解決
参照(reference)を使えば、所有権を移動せずに値を使えます:
fn main() {
let s = String::from("hello");
let len = calculate_length(&s); // &s:sの参照
println!("'{}' の長さは {}", s, len); // ✅ sはまだ使える!
}
fn calculate_length(s: &String) -> usize {
s.len()
} // sは参照なので、実際の値はドロップされない
参照の構文:
& → 参照を作成(borrow)
* → 参照を解決(dereference)
---
5.2 参照と借用
5.2.1 参照のメモリレイアウト
┌─────────────────────────────────────────────────────────┐
│ 参照のメモリレイアウト │
├─────────────────────────────────────────────────────────┤
│ │
│ スタック │
│ ┌────────────┐ │
│ │ s │ │
│ │ ptr ─────┼──┐ │
│ │ len 5 │ │ ヒープ │
│ │ cap 5 │ │ ┌─────────────┐ │
│ └────────────┘ └──────►│ h e l l o │ │
│ └─────────────┘ │
│ ┌────────────┐ │
│ │ r │ │
│ │ ptr ─────┼──┐ │
│ └────────────┘ │ │
│ │ │
│ └──────► sを指す(ヒープではなくsを指す)│
│ │
└─────────────────────────────────────────────────────────┘
重要:
- 参照
rはsを指す(ポインタのポインタのようなもの) - 参照は所有権を持たない
- 参照がスコープを抜けても、元の値は影響を受けない
5.2.2 借用(Borrowing)
参照を作成することを借用(borrowing)と呼びます:
fn main() {
let s = String::from("hello");
borrow_string(&s); // sを借用
println!("{}", s); // sはまだ所有者
}
fn borrow_string(some_string: &String) {
println!("{}", some_string);
} // 借用が終わる(sには影響なし)
借用の比喩:
本の所有権:
- 本を譲渡する(ムーブ)→ もう自分では読めない
- 本を貸す(借用)→ 返してもらえばまた読める
---
5.3 不変参照のルール
5.3.1 複数の不変参照
ルール:同時に複数の不変参照を作れます
fn main() {
let s = String::from("hello");
let r1 = &s;
let r2 = &s;
let r3 = &s;
println!("{}, {}, {}", r1, r2, r3); // ✅ OK
}
なぜ安全?
- どの参照も値を変更しない
- データ競合の心配がない
5.3.2 不変参照では変更できない
fn main() {
let s = String::from("hello");
let r = &s;
// r.push_str(", world"); // ❌ エラー!不変参照では変更不可
}
エラーメッセージ:
error[E0596]: cannot borrow `*r` as mutable, as it is behind a `&` reference
---
5.4 可変参照
5.4.1 可変参照の作成
値を変更したい場合は可変参照(mutable reference)を使用:
fn main() {
let mut s = String::from("hello");
change(&mut s); // &mut:可変参照
println!("{}", s); // "hello, world"
}
fn change(some_string: &mut String) {
some_string.push_str(", world");
}
要件:
- 元の変数が
mutで宣言されている &mutで可変参照を作成
5.4.2 可変参照の制約
重要なルール:
┌─────────────────────────────────────────────────────────┐
│ 可変参照の排他制御ルール │
├─────────────────────────────────────────────────────────┤
│ │
│ 同じスコープで、同じ値に対して: │
│ │
│ ✅ 複数の不変参照 │
│ ✅ 1つの可変参照のみ │
│ ❌ 可変参照と不変参照の混在 │
│ │
└─────────────────────────────────────────────────────────┘
例1:複数の可変参照(エラー)
fn main() {
let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s; // ❌ エラー!2つ目の可変参照
println!("{}, {}", r1, r2);
}
エラー:
error[E0499]: cannot borrow `s` as mutable more than once at a time
なぜ?
- データ競合を防ぐため
- 同時に複数の場所から変更されるのは危険
例2:不変参照と可変参照の混在(エラー)
fn main() {
let mut s = String::from("hello");
let r1 = &s; // 不変参照
let r2 = &s; // 不変参照
let r3 = &mut s; // ❌ エラー!可変参照
println!("{}, {}, {}", r1, r2, r3);
}
エラー:
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
なぜ?
- 不変参照の使用者は「値が変わらない」と期待している
- 可変参照が値を変更すると、その期待が裏切られる
5.4.3 スコープを利用した回避
スコープを分ければOK:
fn main() {
let mut s = String::from("hello");
{
let r1 = &mut s;
r1.push_str(" world");
} // r1がスコープを抜ける
let r2 = &mut s; // ✅ OK
r2.push_str("!");
println!("{}", s); // "hello world!"
}
5.4.4 Non-Lexical Lifetimes (NLL)
Rust 2018以降、参照のスコープは最後に使われた場所まで:
fn main() {
let mut s = String::from("hello");
let r1 = &s;
let r2 = &s;
println!("{}, {}", r1, r2);
// r1とr2はここで最後に使用される
let r3 = &mut s; // ✅ OK(r1, r2はもう使われない)
println!("{}", r3);
}
古いRust(Rust 2015)では:
- 参照のスコープは宣言から
}まで - 上記のコードはエラーになる
---
5.5 ダングリング参照
5.5.1 ダングリング参照とは
ダングリング参照(dangling reference): 無効なメモリを指す参照(C/C++の代表的なバグ)
C言語の例(危険):
int* dangle() {
int x = 5;
return &x; // xはスコープを抜けて破棄される
} // 返される参照は無効なメモリを指す
5.5.2 Rustの保護
Rustはコンパイル時にダングリング参照を防ぎます:
fn dangle() -> &String { // ❌ コンパイルエラー
let s = String::from("hello");
&s
} // sがドロップされる → sへの参照は無効
エラー:
error[E0106]: missing lifetime specifier
--> src/main.rs:1:16
|
1 | fn dangle() -> &String {
| ^ expected named lifetime parameter
|
= help: this function's return type contains a borrowed value,
but there is no value for it to be borrowed from
修正方法:所有権を返す
fn no_dangle() -> String { // ✅ OK
let s = String::from("hello");
s // 所有権を移動
}
---
5.6 参照の実践パターン
5.6.1 読み取り専用アクセス
fn first_word(s: &String) -> &str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}
&s[..]
}
fn main() {
let my_string = String::from("hello world");
let word = first_word(&my_string);
println!("最初の単語: {}", word);
}
5.6.2 変更を伴う処理
fn capitalize(s: &mut String) {
if let Some(first_char) = s.chars().next() {
let rest = s.split_off(1);
s.clear();
s.push(first_char.to_ascii_uppercase());
s.push_str(&rest);
}
}
fn main() {
let mut text = String::from("hello");
capitalize(&mut text);
println!("{}", text); // "Hello"
}
5.6.3 複数の参照を返す
fn split_at_index(s: &String, index: usize) -> (&str, &str) {
(&s[..index], &s[index..])
}
fn main() {
let s = String::from("hello world");
let (first, second) = split_at_index(&s, 5);
println!("前半: {}, 後半: {}", first, second);
}
---
5.7 スライス型
5.7.1 文字列スライス
文字列スライス(&str):Stringの一部への参照
fn main() {
let s = String::from("hello world");
let hello = &s[0..5]; // "hello"
let world = &s[6..11]; // "world"
// 省略形
let hello = &s[..5]; // 先頭から
let world = &s[6..]; // 末尾まで
let whole = &s[..]; // 全体
}
メモリレイアウト:
s: String
┌─────────────────┐
│ ptr ─────────┐ │
│ len 11 │ │
└──────────────┼──┘
│
▼
┌─────────────────────┐
│ h e l l o w o r l d│
└─────────────────────┘
▲ ▲
│ │
&s[..5] &s[6..]
5.7.2 配列スライス
fn main() {
let a = [1, 2, 3, 4, 5];
let slice = &a[1..3]; // [2, 3]
println!("{:?}", slice);
}
型:
let a: [i32; 5] = [1, 2, 3, 4, 5];
let slice: &[i32] = &a[1..3];
// ^^^^^^ スライス型
---
5.8 参照のベストプラクティス
5.8.1 関数の引数
原則:
┌─────────────────────────────────────────────────────────┐
│ 関数引数の選択ガイド │
├─────────────────────────────────────────────────────────┤
│ │
│ 読み取りのみ → &T │
│ 変更が必要 → &mut T │
│ 所有権が必要 → T │
│ │
└─────────────────────────────────────────────────────────┘
例:
// ✅ 良い:読み取りのみ
fn print_length(s: &String) {
println!("長さ: {}", s.len());
}
// ✅ 良い:変更が必要
fn append_world(s: &mut String) {
s.push_str(" world");
}
// ✅ 良い:所有権が必要
fn consume_string(s: String) {
// sを消費する処理
}
// ❌ 悪い:読み取りだけなのに所有権を取る
fn print_length_bad(s: String) {
println!("長さ: {}", s.len());
} // sがドロップされる(無駄)
5.8.2 Stringと&strの使い分け
// ✅ 良い:&strを受け取る(String、&str両方受け入れ可能)
fn process_text(s: &str) {
println!("{}", s);
}
fn main() {
let string = String::from("hello");
let string_slice = "world";
process_text(&string); // String → &str
process_text(string_slice); // &str → &str
}
// ❌ 悪い:Stringを受け取る(&strを受け入れられない)
fn process_text_bad(s: String) {
println!("{}", s);
}
---
5.9 実践例
例1:文字列の最初の単語を取得
fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[..i];
}
}
&s[..]
}
fn main() {
let my_string = String::from("hello world");
let word = first_word(&my_string);
// my_string.clear(); // ❌ エラー!wordが不変参照を保持中
println!("最初の単語: {}", word);
}
例2:配列の最大値を見つける
fn find_max(numbers: &[i32]) -> Option<&i32> {
if numbers.is_empty() {
return None;
}
let mut max = &numbers[0];
for num in &numbers[1..] {
if num > max {
max = num;
}
}
Some(max)
}
fn main() {
let numbers = [3, 1, 4, 1, 5, 9, 2, 6];
match find_max(&numbers) {
Some(max) => println!("最大値: {}", max),
None => println!("空の配列"),
}
}
例3:構造体と参照
struct User {
name: String,
age: u32,
}
impl User {
// 読み取り専用メソッド
fn display(&self) {
println!("{} ({}歳)", self.name, self.age);
}
// 変更メソッド
fn celebrate_birthday(&mut self) {
self.age += 1;
println!("誕生日おめでとう!");
}
// 消費メソッド
fn into_name(self) -> String {
self.name
}
}
fn main() {
let mut user = User {
name: String::from("Alice"),
age: 30,
};
user.display(); // &self
user.celebrate_birthday(); // &mut self
user.display();
let name = user.into_name(); // self(消費)
// user.display(); // ❌ エラー!userは消費された
println!("名前: {}", name);
}
---
5.10 まとめ
学んだこと
- 参照の基本
&T:不変参照
- &mut T:可変参照
- 所有権を移動せずに値を使える- 借用のルール
- ダングリング参照の防止
- スライス
&str、&[T]次のステップ
次の章では、ライフタイムを学びます:
- 参照の有効期間
- ライフタイム注釈
- ライフタイム省略規則
- The Rust Programming Language - Chapter 4.2
- Rust By Example - Borrowing
- Common Rust Lifetime Misconceptions
---