はじめに
小さな例では変数を個別に定義しても問題ありません。しかし、実際のプログラムでは、関連するデータを 1 つの値にまとめて管理することがよくあります。Rust の struct はそのレコードの形を定義し、impl ブロックはそのレコードに属する操作を提供します。
この実験では、用意された InventoryItem モデルを 4 つの小さな修正で完成させます。フィールドを定義し、有効なアイテムを構築し、&self を使って在庫の金額を計算し、&mut self を使って数量を更新します。最後にプログラムを実行し、保存された状態とメソッドの動作を確認します。
在庫レコードを定義する
このステップでは、1 つの在庫アイテムに属する 3 つのデータを定義します。
用意された Cargo プロジェクトに移動し、ソースファイルを開きます。
cd /home/labex/project/inventory-item
nano src/main.rs
struct InventoryItem { ... } という宣言は、新しいレコード型を定義します。波括弧の中では、各 field に名前、コロン、型を記述します。すべての InventoryItem の値には、これらのフィールドをすべて指定する必要があります。
Step 1 のコメントを、次の 3 行に置き換えます。
name: String,
price_cents: u32,
quantity: u32,
name はテキストを所有し、2 つの u32 フィールドは 0 以上の整数を格納します。末尾のカンマはフィールドを区切るだけでなく、後から行を追加したり並べ替えたりしやすくします。
Ctrl+O で保存し、Enter キーを押してから、Ctrl+X で終了します。Cargo で型定義を確認します。
cargo check
Finished の結果が警告なしで表示されれば、構造体の形が有効で、用意された各値が正しく使えることを示します。コンストラクターにはまだ、未完成のコードを示す一時的なマーカー todo! が残っています。Rust は cargo check 中の todo! を許可しますが、todo! に到達する処理を実行すると panic が発生して停止します。次のステップでプログラムを実行する前に、この部分を置き換えます。
完全なアイテムを構築する
このステップでは、new 関連関数を、完全な InventoryItem を返すコンストラクターに変更します。
impl InventoryItem ブロックは、この型に関連する操作をまとめます。new は self を受け取らないため 関連関数 です。InventoryItem::new(...) のように型を通して呼び出します。一方、value_cents のようなメソッドは self を受け取り、item.value_cents() のように値を通して呼び出します。戻り値の型 Self は、この impl ブロック内では InventoryItem を表す別名です。
ソースファイルを開きます。
nano src/main.rs
new の中にある todo!(...) の行全体を、次のコードに置き換えます。
Self { name, price_cents, quantity }
これは構造体式です。Rust のフィールド省略記法では、引数名とフィールド名が同じ場合、name は name: name を意味します。この式には末尾のセミコロンがありません。関数から返す値そのものだからです。
保存して終了したら、プログラムを確認して実行します。
cargo check
cargo run --quiet
この時点で、出力は次のようになります。
Preparing Notebook at 250 cents with 4 units
Item: Notebook
Unit price: 250 cents
Starting value: 0 cents
Adding 3 units
Quantity after restock: 4
Final value: 0 cents
指定したフィールドの値を持つアイテムが作成されました。金額が 0 のままで、数量も変わっていないのは、残り 2 つのメソッドがまだプレースホルダーだからです。この段階では想定どおりの結果です。
&self で金額を計算する
このステップでは、所有権を取得したりアイテムを変更したりせずに、2 つのフィールドを読み取るメソッドを完成させます。
パラメーター &self は、現在の InventoryItem への共有参照です。具体的な型が &InventoryItem である借用値を簡略化した記法です。item.value_cents() と呼び出すと、メソッドが自動的に &self を受け取ります。
ソースファイルを開きます。
nano src/main.rs
value_cents の中にある 0 のプレースホルダーだけを、次のコードに置き換えます。
self.price_cents * self.quantity
ドット記法で、借用したアイテムのフィールドを選択します。乗算によって合計金額をセント単位で求めます。末尾にセミコロンがないため、この式がメソッドの戻り値となり、型は u32 になります。
保存して終了したら、プログラムを実行します。
cargo run --quiet
金額に関する行が次のように表示されます。
Starting value: 1000 cents
...
Final value: 1000 cents
250 セントの商品が 4 個あるため、合計は 1000 セントです。restock がまだ数量を変更していないため、両方の行は同じ値になります。次の最後のステップで、数量を意図的に変更します。
&mut self で在庫を補充する
このステップでは、排他的な借用アクセスを通じて 1 つのフィールドを変更するメソッドを完成させます。
レシーバー &mut self は、現在のアイテムへの変更可能な参照です。所有権を取得せずに、メソッドからフィールドを更新できます。main のバインディングはすでに let mut item として宣言されているため、この一時的な変更可能アクセスを提供できます。
ソースファイルを開きます。
nano src/main.rs
restock 内にある Step 4 のコメントを、次のコードに置き換えます。
self.quantity += amount;
+= 演算子は、右側の値を既存のフィールドに加算し、その結果を同じフィールドに戻します。これは文なので、末尾にセミコロンを付けます。
保存して終了したら、完成したモデルを確認して実行します。
cargo check
cargo run --quiet
完全な出力は次のようになります。
Preparing Notebook at 250 cents with 4 units
Item: Notebook
Unit price: 250 cents
Starting value: 1000 cents
Adding 3 units
Quantity after restock: 7
Final value: 1750 cents
数量が 4 から 7 に変わり、同じ value_cents メソッドが更新後の状態を読み取っています。restock の処理が終わると変更可能な借用も終了するため、その後のコードではアイテムを通常どおり読み取れます。
まとめ
関連するフィールドをまとめて保持する構造体を定義し、Self コンストラクターで値を作成し、&self メソッドで状態を読み取り、&mut self メソッドで状態を更新しました。最後の在庫出力から、構築、計算、変更が連携して動作することを確認できました。


