構造体とメソッドでレコードをモデル化する

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

はじめに

小さな例では変数を個別に定義しても問題ありません。しかし、実際のプログラムでは、関連するデータを 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 メソッドで状態を更新しました。最後の在庫出力から、構築、計算、変更が連携して動作することを確認できました。