はじめに
Rust は所有権を使って、プログラムのどの部分が値に対する責任を持つのか、またその値をいつ解放できるのかを決定します。所有権は、特定の変数名に永久に付くラベルではなく、責任と使用許可だと考えてください。この責任は、新しい束縛へ移すことができます。これにより、ガベージコレクターを必要とせずに、いくつかのメモリエラーを防げます。
この実験では、まず小さな整数がコピーされることを確認します。次に、所有権がムーブされた後の String を意図的に使用し、コンパイラーの診断メッセージを読み取ります。その後、関数から返された所有権を使ってデータの流れを修正します。コードの枠組みはあらかじめ用意されているため、変更は小さく、目的に集中できます。
スコープとコピーされた整数を確認する
このステップでは、小さな整数を内側のスコープへコピーし、元の束縛も引き続き使用できることを確認します。
用意されているプロジェクトへ移動し、ソースファイルを開きます。
cd /home/labex/project/ownership-demo
nano src/main.rs
スコープとは、束縛が有効な範囲です。波括弧で新しい内側のスコープを作成できます。束縛 local_count は内側の波括弧の間でのみ存在し、warehouse_count は外側の main スコープに属します。
Step 1 の行で、0 だけを warehouse_count に置き換えます。
let local_count = warehouse_count; // Step 1: replace the placeholder.
整数型 i32 は Rust の Copy 動作を実装しています。local_count に代入すると、小さな値がコピーされるため、warehouse_count は無効になりません。
Ctrl+O で保存し、Enter を押してから、Ctrl+X で終了します。プログラムを実行します。
cargo run --quiet
最初の 2 行によって、両方の束縛を使用できることが確認できます。
Local count: 3
Warehouse count: 3
内側のスコープが終了すると、local_count は無効になります。一方、外側の warehouse_count は main の終わりまで有効です。
意図的なムーブエラーを読む
このステップでは、ムーブ後の String を意図的に使用し、Rust のコンパイラー診断を読み取ります。チェックが失敗するのは想定どおりであり、このステップでの学習結果です。
String::from("PX-104") は、所有されるテキストを作成します。小さな i32 とは異なり、String はサイズを変更できる、別途確保されたテキストバッファーを管理します。Rust は、このバッファーを 1 つの所有者に任せることで、正確に 1 回だけ解放できるようにします。
record_parcel(parcel_id) の呼び出しでは、String が値として渡されます。所有権は parcel_id 束縛から関数内の引数へムーブします。関数は値を返し、呼び出し側では返された所有者を _returned_id に格納します。先頭のアンダースコアは、この一時的な束縛を現時点では意図的に使用しないことを Rust に伝えます。
値はムーブされたときに破棄されるわけではありません。元の束縛がその値を使用する権限を失うだけです。このプログラムにおける所有権の経路は次のとおりです。
parcel_id in main → parcel_id parameter in record_parcel → _returned_id in main
どの時点でも、同じテキストに対する責任を持つ現在の所有者は 1 つだけです。
ファイルを開きます。
nano src/main.rs
Step 2 のコメントの下にある行を探し、先頭の // だけを削除して、次の状態にします。
println!("Original after move: {parcel_id}");
保存して終了します。次のコマンドを実行する前に、結果を予測してください。parcel_id はすでに所有者ではないため、コンパイラーはこの使用を拒否するはずです。
チェックを実行します。失敗するのが想定された結果です。
cargo check
診断メッセージの次の安定した部分を確認します。
error[E0382]: borrow of moved value: `parcel_id`
...
value moved here
...
value borrowed here after move
println! は値を表示するための一時的なアクセスだけを必要とするため、borrow という語が表示されます。しかし、所有権はすでにムーブされているため、parcel_id を通じてそのアクセスを行うことはできません。次のステップでは、関数から所有権を受け取った束縛を使用します。
返された所有者を使って続行する
このステップでは、record_parcel から返された値に名前を付けて使用し、ムーブエラーを修正します。
関数シグネチャ fn record_parcel(parcel_id: String) -> String は、この関数が String の所有権を受け取り、String を返すことを示しています。最後にある parcel_id 式によって、所有権が呼び出し元へ戻されます。
ファイルを開きます。
nano src/main.rs
2 か所を変更します。まず、関数呼び出しの行にある _returned_id を returned_id に変更します。これから値を使用するため、一時的なアンダースコア付きの束縛を通常の名前に変更します。
次に、エラーになる表示行を次の行に置き換えます。
println!("Returned owner: {returned_id}");
元の parcel_id 束縛は、ムーブ後も無効なままです。_returned_id を returned_id に変更しても、元の束縛が復活するわけではありません。すでに所有権を受け取っている束縛に、通常の名前を付けるだけです。現在の所有者は returned_id なので、使用すべき正しい名前も returned_id です。
保存して終了し、修正したプログラムをチェックします。
cargo check
E0382 エラーがなく Finished と表示されれば、所有権の流れが有効であることを確認できます。プログラムを実行します。
cargo run --quiet
完全な出力は次のようになります。
Local count: 3
Warehouse count: 3
Recorded parcel: PX-104
Returned owner: PX-104
main の終わりで returned_id がスコープを抜けると、Rust は所有されている String を解放します。このデータに対する責任を持つ所有者は、引き続き 1 つだけです。
まとめ
字句スコープと値のライフタイムの関係を確認し、元の束縛を無効にせずに i32 がコピーされることを学びました。また、String が関数へムーブされる流れを追い、E0382 を読み取り、呼び出し元へ返された所有権を安全に使って処理を続けました。



