参照を使ってデータを借用する

RustBeginner
オンラインで実践に進む

はじめに

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 を使用しました。どちらの場合も呼び出し元は所有権を保持しており、コンパイラーが、借用したデータの読み取りと変更の違いを適切に守らせてくれました。