参照と借用
所有権を移動させずにデータを使う「参照」と「借用」について学びます。
問題: 所有権を移動させたくない
前回の例を思い出してください:
fn main() {
let s = String::from("hello");
let len = calculate_length(s); // 所有権がムーブ
// println!("{}", s); // エラー!
}
fn calculate_length(s: String) -> usize {
s.len()
}
値を使いたいだけなのに、所有権を渡さなければいけないのは不便です。
解決策: 参照(Reference)
参照を使うと、所有権を移動させずにデータにアクセスできます。
fn main() {
let s = String::from("hello");
let len = calculate_length(&s); // 参照を渡す
println!("'{}'の長さは{}", s, len); // sはまだ使える!
}
fn calculate_length(s: &String) -> usize {
s.len()
}
参照の図解
let s = String::from("hello");
let r = &s;
スタック ヒープ
┌─────────────┐
│ s │ ┌───────────┐
│ ptr ─────────────────────→│ "hello" │
│ len: 5 │ └───────────┘
│ cap: 5 │ ↑
└─────────────┘ │
↑ │
┌──────┴──────┐ │
│ r (&s) │ │
│ ptr ────────┘ (sを指す)
└─────────────┘
rはsを参照しています。sの所有権は移動しません。
借用(Borrowing)
参照を作ることを借用(Borrowing)と呼びます。
本を図書館から借りるイメージです:
- 図書館(所有者)は本を持っている
- あなた(借用者)は本を読める
- 読み終わったら返す(参照のスコープが終わる)
- 本は図書館のまま(所有権は移動しない)
不変参照(Immutable Reference)
&で作る参照は読み取り専用です。
fn main() {
let s = String::from("hello");
let r1 = &s;
let r2 = &s; // 複数の不変参照OK
println!("{}, {}", r1, r2);
}
不変参照のルール
- 複数の不変参照を同時に持てる
- 参照先のデータを変更できない
fn main() {
let s = String::from("hello");
let r = &s;
// r.push_str(" world"); // エラー!不変参照からは変更できない
}
可変参照(Mutable Reference)
&mutで作る参照は変更可能です。
fn main() {
let mut s = String::from("hello");
change(&mut s);
println!("{}", s); // "hello, world"
}
fn change(s: &mut String) {
s.push_str(", world");
}
可変参照のルール(重要!)
ルール: 可変参照は同時に1つだけ
fn main() {
let mut s = String::from("hello");
let r1 = &mut s;
// let r2 = &mut s; // エラー!可変参照は1つだけ
println!("{}", r1);
}
なぜ1つだけ?
データ競合を防ぐためです。
データ競合は以下の3つが同時に起きると発生します:
- 2つ以上のポインタが同じデータにアクセス
- 少なくとも1つが書き込みを行う
- 同期機構がない
Rustは「可変参照は1つだけ」というルールで、コンパイル時にこれを防ぎます。
不変参照と可変参照の混在
不変参照がある間は、可変参照を作れません
fn main() {
let mut s = String::from("hello");
let r1 = &s; // OK
let r2 = &s; // OK
// let r3 = &mut s; // エラー!不変参照があるので可変参照は作れない
println!("{}, {}", r1, r2);
}
ただし、不変参照が使われなくなった後なら可変参照を作れます:
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);
}
参照のルールまとめ
┌────────────────────────────────────────────────────┐
│ 参照のルール │
├────────────────────────────────────────────────────┤
│ 1. 不変参照(&T)は複数同時に存在できる │
│ 2. 可変参照(&mut T)は同時に1つだけ │
│ 3. 不変参照と可変参照は同時に存在できない │
│ 4. 参照は常に有効でなければならない │
└────────────────────────────────────────────────────┘
ダングリング参照
ダングリング参照とは、解放されたメモリを指す参照です。
fn main() {
let r = dangle();
}
fn dangle() -> &String { // コンパイルエラー!
let s = String::from("hello");
&s // sへの参照を返そうとしている
} // ← sがドロップされる。参照先がなくなる!
Rustはこれをコンパイル時に防ぎます。
解決策: 所有権を返す
#![allow(unused)]
fn main() {
fn no_dangle() -> String {
let s = String::from("hello");
s // 所有権を返す(参照ではなく値自体)
}
}
実践例
例1: 最長の文字列を返す
fn main() {
let s1 = String::from("hello");
let s2 = String::from("hi");
let result = longest(&s1, &s2);
println!("最長: {}", result);
}
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
('aはライフタイムで、次のドキュメントで学びます)
例2: Vecの要素を変更
fn main() {
let mut numbers = vec![1, 2, 3, 4, 5];
for n in &mut numbers {
*n *= 2; // 各要素を2倍に
}
println!("{:?}", numbers); // [2, 4, 6, 8, 10]
}
まとめ
| 種類 | 構文 | 特徴 |
|---|---|---|
| 不変参照 | &T | 読み取り専用、複数OK |
| 可変参照 | &mut T | 変更可能、1つだけ |
| ルール | 内容 |
|---|---|
| ルール1 | 不変参照は複数OK |
| ルール2 | 可変参照は1つだけ |
| ルール3 | 不変と可変は同時に存在不可 |
| ルール4 | 参照は常に有効(ダングリング禁止) |
確認テスト
Q1. 不変参照(&T)について正しいものはどれですか?
Q2. 可変参照(&mut T)について正しいものはどれですか?
Q3. 以下のコードはコンパイルできますか?let mut s = String::from("hello"); let r1 = &s; let r2 = &mut s; println!("{}, {}", r1, r2);
r1がまだ使用される前に、可変参照r2を作ろうとしています。不変参照と可変参照は同時に存在できません。r1を先に使い切れば、その後で可変参照を作ることができます。
Q4. 関数内で文字列を変更したい場合、引数の型として正しいものはどれですか?
&mut Stringが必要です。不変参照&Stringでは変更できず、所有権を受け取ると呼び出し元で使えなくなります。&strは読み取り専用のスライスです。
Q5. 「借用」(Borrowing)の概念について正しい説明はどれですか?
次のドキュメント: 04_lifetimes_basic.md