Rustの借用
所有権のページでは、ひとつ困ったことがありました。String を関数に渡すとムーブされてしまい、そのあと使えなくなるのでした。値をいちいちムーブしたり clone したりするのは大変です。そこで Rust が用意しているのが借用(borrowing)です。
借用とは、所有権を渡さずに、値を使わせてあげることです。友だちに本を貸すのと同じです。友だちは本を読んだら返してくれますし、本の持ち主はずっとあなたのままです。
参照:& で借りる
Section titled “参照:& で借りる”値の前に & を付けると、参照(reference)が作れます。参照を使うと、関数はデータを所有せずに中身を見ることができます。
fn print_length(s: &String) { // s を借りるだけで、所有はしない println!("長さ: {}", s.len());}
fn main() { let text = String::from("hello"); print_length(&text); // text を関数に貸す println!("まだ自分のもの: {text}"); // ✅ text はここでもまだ使える}実行結果:
長さ: 5まだ自分のもの: hello(この例では文字列の長さを表示するので、"hello" は英語のままにしています。日本語の文字は UTF-8 で1文字3バイトあるため、.len() の結果が変わってしまうからです。)
&text は「この関数に text を貸してあげて」という意味です。関数は借りただけなので、所有権は main に残ったままです。だから、呼び出したあとも text を使い続けられます。所有権のページでは、& のない同じようなコードがエラーになっていたことを思い出してください。
可変参照:借りて書き換える
Section titled “可変参照:借りて書き換える”ふつうの & 参照は読み取り専用です。見ることはできても、書き換えることはできません。借りた値を関数の中で書き換えたいときは、&mut を使って可変参照(mutable reference)にします。
fn add_excitement(s: &mut String) { s.push_str("!!!");}
fn main() { let mut text = String::from("こんにちは"); add_excitement(&mut text); // 書き換えOKとして貸す println!("{text}"); // こんにちは!!!}実行結果:
こんにちは!!!ここでは3つのことがそろっている点に注目してください。text が mut で宣言されていること、関数が &mut String を受け取ること、そして呼び出すときに &mut text を渡していることです。借りた値を書き換えるには、この3つすべてが必要です。
借用のルール
Section titled “借用のルール”Rust は参照について2つのルールを守らせます。これはデータ競合(data race)を防ぐためのものです。データ競合とは、2か所のコードが同じデータに同時に触れて、データを壊してしまう種類のバグのことです。
ある時点で持てるのは、次のどちらか一方です。
- 読み取り専用の参照(
&)をいくつでも、または - 可変参照(
&mut)をちょうどひとつ
ただし、両方を同時に持つことはできません。
かんたんに言えば、「読む人は何人いてもいいけれど、書く人はひとりだけ。読む人と書く人が同時にいてはいけない」ということです。次のコードはこのルールを破っています。
fn main() { let mut text = String::from("こんにちは");
let r1 = &text; // 読み取り専用で借りる let r2 = &mut text; // ❌ r1 が生きている間は、書き換え用に借りられない
println!("{r1} {r2}");}コンパイラはここで止めてくれます。
error[E0502]: cannot borrow `text` as mutable because it is also borrowed as immutableこのメッセージは「text はすでに不変(読み取り専用)として借用されているので、可変として借用することはできません」という意味です。
借用はいつ終わる?
Section titled “借用はいつ終わる?”借用はスコープの閉じ } まで続く、と思うかもしれません。実はそうではありません。借用が続くのは、その参照が最後に使われる場所までです。そこから先は、値をまた借用できます。可変で借りることもできます。
つまり、上のエラーになる例は、r2 を作る前に r1 を使い終えるだけで直せるのです。
fn main() { let mut text = String::from("こんにちは");
let r1 = &text; println!("読み取り中: {r1}"); // r1 を使うのはここが最後なので、借用はここで終わる
let r2 = &mut text; // ✅ OK:もう読んでいる人はいない r2.push_str("、世界");
println!("{text}");}実行結果:
読み取り中: こんにちはこんにちは、世界ルールは変わらず「読む人は何人でも、または書く人はひとりだけ」です。ただ、それぞれの参照が実際に使われている範囲で判定されるだけです。書く人の r2 が登場する時点で、読む人の r1 はもう用が済んでいます。そして最後の println! がもう一度 text を読む前に、r2 も用が済んでいます。どこも重なっていないので、Rust は満足してくれます。
これが借用エラーのいちばんよくある直し方です。順番を入れ替えましょう。読み取りと書き込みを混ぜずに、必要な読み取りを全部済ませてから書き込む(またはその逆)ようにします。
なぜこれが大切なのか
Section titled “なぜこれが大切なのか”これらのルールはすべてコンパイル時にチェックされます。そのため、プログラムの実行時には一切コストがかかりません。Rust が「データ競合が起きない」ことを約束しながら C と同じくらい速く動けるのは、このためです。安全性は、プログラムが動き出す前にすでに証明されているのです。
まずは自分で解いてみましょう。Rust Playground でも、手元のプロジェクトでもかまいません。コンパイラに怒られたら、エラーを読んでみてください。たいていは、借用が重なっている場所をちょうど示してくれます。
1. 受け取らずに数える
Section titled “1. 受け取らずに数える”Vec<String> を借用して、すべての単語の文字数の合計を返す関数 total_letters を書いてください。呼び出したあとも、main でそのベクタを表示できるようにします。
ヒント:引数の型は &Vec<String> で、呼び出すときは &words を渡します。借りたベクタを for word in list でループして、word.len() を足していきましょう。
解答を見る
fn total_letters(list: &Vec<String>) -> usize { let mut total = 0; for word in list { total += word.len(); } total}
fn main() { let words = vec![String::from("borrow"), String::from("check")]; let count = total_letters(&words); // ベクタを貸す println!("{count}文字"); println!("{:?} はまだ main のもの", words);}実行結果:
11文字["borrow", "check"] はまだ main のもの関数は単語を見るだけなので、読み取り専用の & で借りれば十分です。もし list: Vec<String> と書いていたら、ベクタは関数へムーブしてしまい、最後の println! がコンパイルできなくなります。
なお、単語は英語のままにしています。.len() が数えるのは文字数ではなくバイト数で、日本語の文字は1文字あたり3バイトになるため、日本語にすると結果が変わってしまうからです。
2. 買い物リスト
Section titled “2. 買い物リスト”買い物リストに品物を追加する add_item と、すべての品物を表示する print_list の2つの関数を書いてください。品物を3つ追加してから、リストを表示します。
ヒント:一方の関数はリストを変更し、もう一方は読むだけです。&mut Vec<String> が必要なのはどちらで、&Vec<String> で足りるのはどちらでしょうか。&str を String に変えるには String::from(item) を使います。
解答を見る
fn add_item(list: &mut Vec<String>, item: &str) { list.push(String::from(item));}
fn print_list(list: &Vec<String>) { for item in list { println!("- {item}"); }}
fn main() { let mut list = Vec::new(); add_item(&mut list, "牛乳"); add_item(&mut list, "パン"); add_item(&mut list, "りんご"); print_list(&list);}実行結果:
- 牛乳- パン- りんごadd_item はリストを変更するので &mut で借ります。そのため main の list は mut にする必要があります。print_list は読むだけなので、ふつうの & で十分です。&mut list の借用はそれぞれ呼び出しが戻った時点で終わるので、次の呼び出しでまた借りられるのです。
3. 書く人がふたり同時に
Section titled “3. 書く人がふたり同時に”次のプログラムはコンパイルできません。first と second の両方を残したまま、スコア: 10点 と表示されるように直してください。
fn main() { let mut message = String::from("スコア:"); let first = &mut message; let second = &mut message; first.push_str(" 10"); second.push_str("点"); println!("{message}");}error[E0499]: cannot borrow `message` as mutable more than once at a time --> src/main.rs:4:18 |3 | let first = &mut message; | ------------ first mutable borrow occurs here4 | let second = &mut message; | ^^^^^^^^^^^^ second mutable borrow occurs here5 | first.push_str(" 10"); | ----- first borrow later used hereこのエラーは「message を同時に2回以上、可変として借用することはできません」という意味です。
ヒント:書く人は同時にひとりだけです。上の借用はいつ終わる?を見てみましょう。second を作る前に、first を使い終えられないでしょうか。
解答を見る
fn main() { let mut message = String::from("スコア:");
let first = &mut message; first.push_str(" 10"); // first を使うのはここが最後:借用はここで終わる
let second = &mut message; second.push_str("点");
println!("{message}");}実行結果:
スコア: 10点元のコードでは、4行目で second を作った時点で、first はまだ(5行目で)使われる予定でした。つまり、書く人が同時にふたりいたわけです。first.push_str を上に移せば、second が生まれる前に first の用が済むので、&mut が同時に2つ存在することはなくなります。
- 借用(
&)を使うと、所有権を渡さずに値を使わせられる。値は手元に戻ってくる。 - ふつうの
&参照は読み取り専用。借りた値を書き換えるには&mutを使う。 - 参照を通して書き換えるには、変数・引数・呼び出しのすべてに
mut/&mutが必要。 - ルール:読む人は何人でも、または書く人はひとりだけ。両方同時はダメ。
- 借用は参照が最後に使われた場所で終わる。そのため、コードの順番を入れ替えるだけで借用エラーが直ることが多い。
- これらはすべてコンパイル時にチェックされるので、実行時のコストはゼロ。
理解度チェック
Section titled “理解度チェック”理解度チェック
1. 関数に値を渡すとき、値の前に & を付けるとどうなりますか?
2. 借用のルールで、同時に持つことが許されている組み合わせはどれですか?
3. 参照を通して値を書き換えるには、何が必要ですか?
4. Rust が実行時のパフォーマンスを落とさずに借用のルールを守らせられるのはなぜですか?
スコア: 0 / 4
さらに詳しく
Section titled “さらに詳しく”- 参照と借用 — The Rust Programming Language 日本語版:公式の解説です。宙に浮いた参照(ダングリング参照)と、Rust がそれを作らせない理由も説明しています。
- 借用 — Rust By Example 日本語版:
&による借用の実行できる例です。続くページでミュータビリティ(可変な借用)とエイリアスも扱っています。 - Non-lexical lifetimes — The Edition Guide(英語):借用が閉じ括弧ではなく最後に使われた場所で終わるようになった変更についての解説です。
- スライス型 — The Rust Programming Language 日本語版:文字列や配列の一部だけを借用する方法です。参照の次に学ぶのにぴったりです。
reference— 標準ライブラリのドキュメント(英語):&Tと&mut Tが何なのか、どんなトレイトを実装しているのかがわかります。