はじめに
String を移動すると所有権も移ります。しかし、多くの関数は一時的にアクセスするだけで十分です。Rust では、この一時的なアクセスを借用(borrow)と呼び、参照で表します。
この実験では、用意されたゲストリストのプログラムを3つの小さな段階で完成させます。まず共有参照を使って所有しているテキストを検査し、次に共有アクセスでは変更できない理由を意図的に確認します。最後に、可変参照を使って1回だけ制御された更新を行います。最終的な出力から、main がリストの所有権を保持し、その後も使用できることを確認できます。
共有参照を使ってデータを検査する
このステップでは、String の所有権を関数に移動する代わりに、読み取り専用のアクセスを貸し出します。
用意された Cargo プロジェクトに移動し、ソースファイルを開きます。
cd /home/labex/project/guest-borrowing
nano src/main.rs
セットアップによってプロジェクト全体が作成され、inspect_guests の呼び出しには1つのプレースホルダーが残されています。この関数の引数の型は &String です。& は「String への参照」を意味します。関数は値を一時的に参照できますが、その値の所有者にはなりません。
/* Step 1: borrow guest_list */ だけを &guest_list に置き換え、呼び出しを次の形にします。
inspect_guests(&guest_list);
ここで &guest_list は共有参照を作成します。共有参照では、String 自体の所有権を移動せずに、読み取り専用のアクセスを共有できます。
Ctrl+O で保存し、Enter キーを押してから、Ctrl+X で終了します。プログラムを実行します。
cargo run --quiet
出力は次のようになります。
Inspecting: Ari, Bo
Caller still owns: Ari, Bo
Before update: Ari, Bo
Updated: Ari, Bo
1行目は、借用した参照から出力されています。重要なのは2行目です。inspect_guests が終了した後も、main が guest_list を使用できることを示しています。更新用の関数も現在の値を読み取りますが、まだ値は変更しません。
共有参照の制限を確認する
このステップでは、共有参照を使って String を意図的に変更してみます。コンパイラーによるチェックが失敗するのは想定どおりです。この結果から、Rust の借用ルールを確認できます。
もう一度ソースファイルを開きます。
nano src/main.rs
add_guest の中にある用意済みの行から、先頭の // だけを削除して、次の形にします。
list.push_str(", Mina");
push_str メソッドは String にテキストを追加するため、値を変更する権限が必要です。しかし、現在の list の型は &String であり、共有された読み取り専用のアクセスしか与えません。
保存して終了します。チェックする前に結果を予測してください。共有参照は可変アクセスを提供できないため、コンパイラーはこの変更を拒否するはずです。
チェックを実行します。失敗しても問題ありません。
cargo check
診断メッセージから、次の安定した部分を確認します。
error[E0596]: cannot borrow `*list` as mutable, as it is behind a `&` reference
...
consider changing this to be a mutable reference
*list という表現は、参照を通して到達する String を指しています。Rust は、その String が決して変更できないと言っているのではありません。この特定の &String には、変更する権限がないと言っています。次のステップで、制御された可変アクセスを与えます。
可変参照を使って更新する
このステップでは、add_guest に対して、呼び出し元の String を変更する一時的かつ排他的な権限を与えます。
可変参照は &mut T の形で表します。初心者向けの重要なルールは、同時に許可されるのは複数の読み手、または1つの書き手ということです。共有参照では読み取りができます。一方、可変参照を1つ使うと、一時的に排他的な書き込みアクセスが与えられます。この排他性によって、プログラム内の2つの部分が同じ値を予期せず変更することを防ぎます。
ファイルを開きます。
nano src/main.rs
次の3か所を変更します。
add_guestの引数を&Stringから&mut Stringに変更します。- バインディングに
mutを追加し、let mut guest_list = ...の形にします。 add_guestの呼び出しを&guest_listから&mut guest_listに変更します。
完成した関数シグネチャと呼び出しは、次の形になります。
fn add_guest(list: &mut String) {
add_guest(&mut guest_list);
2つの mut は関連していますが、役割は異なります。let mut guest_list は、所有している値を変更できることを示します。続いて &mut guest_list が一時的な可変参照を1つ作成し、呼び出しの間だけ add_guest に排他的な書き込みアクセスを貸し出します。
保存して終了し、修正をチェックします。
cargo check
Finished が表示され、E0596 が表示されなければ、権限の指定が正しく対応しています。プログラムを実行します。
cargo run --quiet
最終的な出力は次のようになります。
Inspecting: Ari, Bo
Caller still owns: Ari, Bo
Before update: Ari, Bo
Updated: Ari, Bo, Mina
可変借用は add_guest が戻ると終了します。最後の出力では再び guest_list を読み取れます。これは、関数が制御された更新を行った後も、所有権が main に残っていることを示しています。
まとめ
共有された読み取り専用アクセスには &String を使用し、一時的で排他的な変更には &mut String を使用しました。どちらの場合も呼び出し元は所有権を保持しており、コンパイラーが、借用したデータの読み取りと変更の違いを適切に守らせてくれました。



