Problem overview
A shop tracks how many pieces of each item it has: the item's stock. The stock can never go below 0, and it changes only when pieces are sold or new ones arrive. So the stock is a private field, changed only through two methods that check the rules. The item's name has no rules to protect, so any code can read it.
You write the InventoryItem struct with a public field pub name (a String) and a private field stock (an i32), and these functions:
InventoryItem::new(name, stock)returns an item with that name and that many pieces.sell(n)sellsnpieces. Ifnis at least 1 and at most the stock, it lowers the stock bynand returnstrue. Otherwise it changes nothing and returnsfalse.restock(n)addsnpieces whennis at least 1, and ignoresnotherwise.get_stock()returns the stock. It is the only way for other code to read it.
For a pencil with a stock of 5, sell(3) works and leaves 2. Then sell(5) is refused, because only 2 are left, and the stock stays 2. After restock(10) it is 12.
Approach
There are two rules, one per method, and each is a check before a change:
- Selling is refused when
nis below 1 (nothing or a negative number of pieces) or above the stock (more than the shop has). Selling exactly the whole stock is allowed. Because the check comes before any change, a refused sale leaves the stock exactly as it was. - Restocking adds only when
nis at least 1. A delivery of 0 or a negative number isn't a delivery, so it is ignored.
Rust fields are private by default: without pub, code in other modules can't read or change them. That is how stock is protected. The only way to touch it from outside is through sell and restock, and the only way to see it is get_stock. The name is marked pub, so other code can read and even change it directly.
Step by step
- Declare the fields:
pub name: Stringand, below it,stock: i32withoutpub. - Build the item in
new. The parameternameis a&str, soname.to_string()makes the ownedStringthat the struct stores. The stock comes straight from the parameter. - In
sell, refuse first. Anif n < 1 || n > self.stockwhose body returnsfalse. The||means "or": either condition is enough to refuse. - Then sell. Only a sale that passed the check reaches the next line,
self.stock -= n;, so the stock never goes below 0. End withtrue, without a semicolon. - In
restock, guard the addition:if n >= 1 { self.stock += n; }. get_stockreturnsself.stock. It only reads, so it takes&self. The methods that change the item take&mut self.
The solution in Rust
struct InventoryItem {
pub name: String,
stock: i32,
}
impl InventoryItem {
fn new(name: &str, stock: i32) -> InventoryItem {
InventoryItem {
name: name.to_string(),
stock: stock,
}
}
fn sell(&mut self, n: i32) -> bool {
if n < 1 || n > self.stock {
return false;
}
self.stock -= n;
true
}
fn restock(&mut self, n: i32) {
if n >= 1 {
self.stock += n;
}
}
fn get_stock(&self) -> i32 {
self.stock
}
}
Using it with a pencil:
let mut p = InventoryItem::new("Pencil", 5);
println!("{}", p.sell(3));
println!("{}", p.get_stock());
println!("{}", p.sell(5));
println!("{}", p.get_stock());
p.restock(10);
println!("{} {}", p.name, p.get_stock());
It prints:
true
2
false
2
Pencil 12
Rust details worth knowing:
- The early
return.return false;leavessellat once. When the check passes, the function goes on to the last line,true, which has no semicolon and is the returned value. pubon a field. From another module,item.namecan be read and assigned. Forstock, reading it gives the error "fieldstockof structInventoryItemis private". That is whyget_stockexists.stock: stock. This works, but it is longer than the field init shorthand,stockalone. Both mean the same.- Signed numbers. The parameter
nis ani32, so negative values can be passed, which is why the checkn < 1matters. Because the check runs first, the subtraction can never take the stock below 0.
Complexity
Each method does a couple of comparisons and at most one update, so every call is O(1) time, and the struct uses O(1) space.
Edge cases and common mistakes
- Selling everything. For a mug with a stock of 4,
sell(4)returnstrueand leaves 0. With nothing left,sell(1)returnsfalse. Writingn >= self.stockas the refusal test would wrongly refuse the first one. - Selling 0 or a negative number. Both are refused with
falseand the stock doesn't change. A negative sale would otherwise raise the stock. - Restocking 0 or a negative number. Both are ignored. A lamp with a stock of 2 still has 2 after
restock(0)andrestock(-5). - Changing before checking. Subtracting first and then testing the result leaves the stock changed when the sale is refused.
- Returning something from
restock. It returns nothing, so there is notrueorfalseto produce.
Testing
- A mug with a stock of 4:
sell(4)givestrue,get_stock()gives 0,sell(1)givesfalse,get_stock()gives 0. - A lamp with a stock of 2:
sell(0)andsell(-3)givefalse, and afterrestock(0)andrestock(-5)the stock is still 2. - Continuing with the lamp,
restock(1)makes the stock 3, sosell(3)givestrueand the stock is 0.
