Rustの参照(Reference)と借用を徹底解説!所有権を移動させずに値を扱う基本
生徒
「Rustの所有権は分かりましたが、関数に値を渡すたびに元の変数が使えなくなるのは不便です。どうにかなりませんか?」
先生
「そこで登場するのが参照(Reference)です。所有権を移動させずに、値を『一時的に借りる』ことで解決できるんですよ。」
生徒
「一時的に借りる……それなら元の変数も使い続けられるんですね!」
先生
「その通りです。Rustではこの仕組みを借用(Borrowing)と呼びます。メモリ安全を守りつつ、効率的にプログラムを書くための必須知識です。詳しく見ていきましょう。」
1. Rustの所有権(Ownership)とは?
Rustの所有権(Ownership)は、値(データ)を「誰が持っているか」を明確にし、スコープ(有効範囲)と連動してメモリを安全に管理する仕組みです。C言語やC++のように手動でfreeやdeleteを呼ばなくても、Rustは所有権のルールに従って自動的にリソースを解放できます。
しかし、所有権には「一つの値の所有者は常に一人」という厳しい原則があります。変数を別の変数に代入したり、関数の引数に渡したりすると、所有権が移動(ムーブ)してしまい、元の変数は使えなくなります。これを解決し、所有権を手放さずにデータにアクセスする仕組みが「参照」なのです。
2. 参照の基本とアンパサンドの役割
Rustで参照を作るには、変数名の前に &(アンパサンド)を付けます。参照とは、データの所有権を持つのではなく、そのデータがメモリ上のどこにあるかという「場所の情報」だけを持つ仕組みです。これを利用することを「借用」と呼びます。本そのものを譲り受けるのが所有権の移動なら、本を借りて読むのが参照です。
参照を使う最大のメリットは、データのコピーが発生しないため、巨大な構造体や長い文字列を扱う際にメモリ消費を抑え、実行速度を高速に保てることです。また、参照は所有権を持たないため、参照のスコープが終わっても元のデータが破棄されることはありません。以下のコードで、参照の基本的な書き方を確認してみましょう。
fn main() {
let s1 = String::from("Rustの学習");
// &s1 と書くことで s1 の所有権を貸し出す(借用)
let len = calculate_length(&s1);
// 所有権は戻ってきているので、ここで s1 を再び使える
println!("「{}」の長さは {} です。", s1, len);
}
fn calculate_length(s: &String) -> usize {
s.len()
} // s はスコープを抜けるが、参照なので元のデータは破棄されない
3. 不変の参照による安全なデータ共有
Rustの参照には「不変の参照」と「可変の参照」の二種類があります。デフォルトは不変の参照(&T)です。これは、参照先のデータを読み取ることはできますが、中身を書き換えることはできないという制約があります。
不変の参照の素晴らしい点は、同時に複数の場所から同じデータを参照できることです。データが勝手に書き換わらないことが保証されているため、複数のスレッドや関数で安全にデータを共有できます。Rustのコンパイラは、不変の参照が存在している間に、誰かがそのデータを書き換えないように厳格にチェックしています。これにより、他の言語で頻発する不整合な読み取りバグを未然に防いでいます。
4. 可変の参照とデータの書き換え
もし、借りた先でデータを変更したい場合は、可変の参照(&mut T)を使用します。これを使うには、元の変数が mut(ミュータブル)として宣言されている必要があります。可変の参照を使えば、所有権を移動させることなく、関数の中で値を更新することが可能です。
可変の参照は非常に強力な権限を持つため、Rustでは厳しい制限を設けています。それは「ある特定のスコープ内で、可変の参照はたった一つしか存在できない」というルールです。さらに、可変の参照がある間は、不変の参照を同時に作ることもできません。この「たった一人の書き手」という原則が、データ競合という最も厄介なバグをコンパイル時に完全に排除します。
fn main() {
let mut my_score = 100;
// 可変の参照 &mut を渡す
add_bonus(&mut my_score);
println!("現在のスコア: {}", my_score);
}
fn add_bonus(score: &mut i32) {
*score += 50; // アスタリスクを使って参照先の値を書き換える
}
5. 借用チェッカーが守るメモリ安全性の境界線
Rustには「借用チェッカー(Borrow Checker)」という非常に優秀なコンパイラの機能が備わっています。これは、参照の寿命が元のデータの寿命を超えていないか、また先述の参照ルール(不変・可変の共存制限)が守られているかを監視する仕組みです。
例えば、あるデータが破棄された後に、そのデータを指していた参照を使おうとすると、借用チェッカーが即座にエラーを出します。これをダングリングポインタ(宙に浮いたポインタ)問題と呼び、C言語などでは深刻なセキュリティホールやクラッシュの原因となってきました。Rustではこのチェックが自動で行われるため、プログラマはメモリの安全性を確信しながらコーディングに集中できるのです。
6. 参照のスコープとライフタイムの基本
参照が有効な範囲のことをライフタイム(生存期間)と呼びます。多くの場合、Rustは関数のスコープに基づいてライフタイムを自動的に推論してくれます。参照は、指し示している元の変数がスコープを抜ける前に、必ず使い終わっていなければなりません。
初心者がよく遭遇するエラーに「参照の寿命が短すぎる」というものがありますが、これは所有権のルールと参照のルールが衝突しているサインです。Rustでは、波括弧 {} を使って変数の寿命を意図的に制御することで、参照の有効範囲を調整することができます。この厳密な管理こそが、ガベージコレクションなしでメモリ安全を実現している魔法の正体です。
fn main() {
let s = String::from("一時的なデータ");
{
let r = &s; // s を参照する r の作成
println!("内部スコープ: {}", r);
} // r はここで消えるので問題なし
println!("外部スコープでも s は有効: {}", s);
}
7. 参照外しによる値の操作とデリファレンス
参照を使ってデータの中身を操作する際、*(アスタリスク)という記号を使うことがあります。これを「参照外し(Dereferencing)」と呼びます。参照はあくまで住所情報を指しているため、その住所にある実体そのものにアクセスするには、このデリファレンス操作が必要です。
数値などのプリミティブ型を可変の参照経由で変更する場合によく使われます。ただし、Rustには「ドット演算子による自動参照解除」という非常に便利な機能があり、メソッドを呼び出す際にはコンパイラが自動的に * を補ってくれるため、日常的なコードでは意識せずに書けることも多いです。この絶妙なバランスがRustの使い勝手を支えています。
8. 文字列スライスという特殊な参照の形
文字列を扱う際に頻繁に登場する &str(文字列スライス)も参照の一種です。これは String 型全体ではなく、その一部を指し示す参照です。関数の引数に &String ではなく &str を使うと、String の参照も文字列リテラルもどちらも受け取れるようになり、関数の汎用性が一気に高まります。
スライスは「ポインタ」と「長さ」の二つの情報を持った特殊な参照であり、これを利用することでメモリ割り当てを最小限に抑えながら、柔軟な文字列操作が可能になります。これも所有権を移動させない「借用」の考え方がベースとなっており、Rustらしい効率的なコーディングの典型例と言えます。
fn main() {
let text = String::from("Rust Programming");
// 文字列スライスによる部分的な借用
let part = &text[0..4];
display_info(part);
display_info("直接のリテラル"); // リテラルも &str なので渡せる
}
fn display_info(s: &str) {
println!("表示: {}", s);
}
9. データ競合を物理的に不可能にする参照のルール
ここで改めて、なぜRustが参照の数に制限をかけているのかを考えましょう。多くのプログラミング言語では、一つのデータに対して複数の書き手が同時にアクセスでき、これが原因で原因不明のクラッシュが発生します。Rustの「不変なら複数OK、可変なら唯一人」というルールは、この「データ競合」を言語仕様のレベルで不可能にします。
この仕様があるおかげで、マルチスレッド環境でのプログラミングが驚くほど簡単になります。他の言語ではロック(Mutex)などを複雑に組み合わせて慎重に書かなければならない部分も、Rustならコンパイラが安全性を保証してくれるため、ランタイムエラーを恐れずに高性能なコードを書くことができるのです。
10. 参照を使いこなすための最初の一歩
Rustを学び始めたばかりの方は、まずは「関数の引数にはとりあえず & を付けて参照で渡す」という習慣をつけるのがおすすめです。所有権を移動させると、その後のコードで変数が使えなくなり、パズルのように難しくなってしまいます。参照を基本とすることで、他の言語に近い感覚でプログラムを書き進めることができます。
もしコンパイラに「所有権が必要です」や「可変の参照が重複しています」と怒られたら、そこで初めてデータの持ち方や書き換えのタイミングを見直せばよいのです。エラーメッセージはあなたを助けるためのアドバイスです。参照と借用をマスターすれば、メモリを完璧にコントロールするRustの真の力を実感できるようになるでしょう。焦らず、一歩ずつこの強力な仕組みに慣れていってください。
Rustの参照(&)についての解説、いかがでしたか?所有権を理解した上で参照を使いこなせるようになれば、Rust中級者への道はすぐそこです。メモリを大切に扱いながら、高速で安全なアプリケーションを作っていきましょう!