Model Records with Structs and Methods

RustBeginner
Practice Now

Introduction

Separate variables work for tiny examples, but real programs often need one value that keeps related data together. A Rust struct defines that record shape, and an impl block gives the record operations that belong to it.

You will complete a prepared InventoryItem model in four small edits: define its fields, construct a valid item, calculate its stock value through &self, and update its quantity through &mut self. The final program will prove both the stored state and the method behavior.

Define the Inventory Record

In this step, you will define the three pieces of data that belong to one inventory item.

Enter the prepared Cargo project and open its source file:

cd /home/labex/project/inventory-item
nano src/main.rs

The declaration struct InventoryItem { ... } introduces a new record type. Inside the braces, each field has a name followed by a colon and its type. Every InventoryItem value must provide all of these fields.

Replace the Step 1 comment with these three field lines:

    name: String,
    price_cents: u32,
    quantity: u32,

name owns text, while the two u32 fields store non-negative whole numbers. The trailing commas separate fields and make it easy to add or reorder lines later.

Save with Ctrl+O, press Enter, and exit with Ctrl+X. Ask Cargo to check the type definitions:

cargo check

A Finished result without warnings means the struct shape is valid and every prepared value has a clear use. The constructor still contains todo!, a temporary marker for unfinished code. Rust allows it during cargo check, but running a path that reaches todo! would stop with a panic. You will replace it before running the program in the next step.

Construct a Complete Item

In this step, you will turn the new associated function into a constructor that returns a complete InventoryItem.

The impl InventoryItem block groups operations with the type. new is an associated function because it does not receive self; it is called through the type with InventoryItem::new(...). A method such as value_cents receives self and is called through a value with item.value_cents(). The return type Self is another name for InventoryItem inside this impl block.

Open the source:

nano src/main.rs

Inside new, replace the entire todo!(...) line with:

        Self { name, price_cents, quantity }

This is a struct expression. Rust's field shorthand lets name mean name: name when the parameter and field share a name. The expression has no semicolon because it is the value returned from the function.

Save and exit, then check and run the program:

cargo check
cargo run --quiet

The current output should be:

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

The item now exists with the supplied field values. The zeros and unchanged quantity come from the two remaining method placeholders, so they are expected at this stage.

Calculate Value with &self

In this step, you will complete a method that reads two fields without taking ownership or changing the item.

The parameter &self is a shared reference to the current InventoryItem. It is shorthand for a borrowed value whose concrete type is &InventoryItem. A method receives it automatically when you call item.value_cents().

Open the source:

nano src/main.rs

Inside value_cents, replace only the 0 placeholder with:

        self.price_cents * self.quantity

The dot selects fields from the borrowed item. Multiplication produces the total value in cents, and the expression becomes the method's returned u32 because it has no trailing semicolon.

Save and exit, then run:

cargo run --quiet

The value lines should now read:

Starting value: 1000 cents
...
Final value: 1000 cents

Four units at 250 cents equal 1000 cents. Both lines still match because restock has not changed the quantity yet; that controlled mutation is the final step.

Restock with &mut self

In this step, you will complete a method that changes one field through exclusive borrowed access.

The receiver &mut self is a mutable reference to the current item. It allows the method to update fields without taking ownership. The binding in main is already declared with let mut item, so it can provide that temporary mutable access.

Open the source:

nano src/main.rs

Replace the Step 4 comment inside restock with:

        self.quantity += amount;

The += operator adds the value on the right to the existing field and stores the result back in that field. This is a statement, so it ends with a semicolon.

Save and exit, then check and run the finished model:

cargo check
cargo run --quiet

The complete output should be:

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

The quantity changes from four to seven, and the same value_cents method observes the new state. The mutable borrow ends after restock, so later code can read the item normally.

Summary

You defined a struct that keeps related fields together, built values through a Self constructor, read state with an &self method, and updated state with an &mut self method. The final inventory output proved that construction, calculation, and mutation work together.