Step 4 of 7
Struct fields, privacy and pub(crate)
Privacy also applies to the fields of a struct. pub struct makes the type usable outside its module, but each field stays private unless it's marked pub too. That's how Rust protects invariants: rules that must always hold, like "a balance never goes below zero". If only the module's own methods can touch the field, the rule is checked in exactly one place.
mod bank {
pub struct Account {
pub owner: String,
balance: u64,
}
impl Account {
pub fn new(owner: &str) -> Account {
Account { owner: owner.to_string(), balance: 0 }
}
pub fn deposit(&mut self, amount: u64) {
self.balance += amount;
}
pub fn withdraw(&mut self, amount: u64) -> Result<(), String> {
if amount > self.balance {
return Err(format!("insufficient funds: have {}, need {amount}", self.balance));
}
self.balance -= amount;
Ok(())
}
pub fn balance(&self) -> u64 {
self.balance
}
}
}
fn main() {
let mut acct = bank::Account::new("Ann");
acct.deposit(100);
println!("{:?}", acct.withdraw(30));
println!("{:?}", acct.withdraw(500));
println!("{} has {}", acct.owner, acct.balance());
}
Ok(())
Err("insufficient funds: have 70, need 500")
Ann has 70
- Outside
bank, code can read and changeowner(it'spub) but can't touchbalance. The only ways to change the balance aredepositandwithdraw, which keep it valid. - The getter
balance()gives read-only access. Naming it like the field (balance, notget_balance) is the Rust convention. - Because a field is private, code outside the module can't build an
Accountwith a struct literal. It has to go throughnew: the constructor pattern.
The errors you'll see
Reading a private field:
mod bank {
pub struct Account {
pub owner: String,
balance: u64,
}
impl Account {
pub fn new(owner: &str) -> Account {
Account { owner: owner.to_string(), balance: 0 }
}
}
}
fn main() {
let acct = bank::Account::new("Ann");
println!("{} has {}", acct.owner, acct.balance);
}
error[E0616]: field `balance` of struct `Account` is private
Building the struct directly, skipping the constructor:
mod bank {
pub struct Account {
pub owner: String,
balance: u64,
}
}
fn main() {
let acct = bank::Account { owner: String::from("Ann"), balance: 1_000_000 };
println!("{}", acct.owner);
}
error[E0451]: field `balance` of struct `Account` is private
Nice try, Ann.
Enums are different
When an enum is pub, all its variants (and their data) are public too. An enum with hidden variants would be useless: you couldn't match on it.
Finer control: pub(crate) and pub(super)
| Written | Visible to |
|---|---|
pub |
everyone who can see the parent module; in a library, other crates too |
pub(crate) |
the whole crate, but never other crates |
pub(super) |
the parent module (and the parent's other children) |
| nothing | this module and the modules inside it |
mod app {
pub(crate) fn version() -> &'static str {
"1.4.0"
}
pub mod ui {
pub(super) fn theme() -> &'static str {
"dark"
}
}
pub fn banner() -> String {
format!("v{} ({})", version(), ui::theme())
}
}
fn main() {
println!("{}", app::banner());
println!("{}", app::version());
}
v1.4.0 (dark)
1.4.0
main may call app::version() (it's pub(crate)), but app::ui::theme() would fail with error[E0603]: function `theme` is private: only app can see it. In a program (a binary crate), pub and pub(crate) behave the same. The difference matters in libraries, where every pub item is part of your public API, a promise other crates rely on. pub(crate) lets modules share helpers without making that promise.
Your task
Finish the inventory module. Item has a public name and a private qty, so the quantity can never go negative. Implement:
Item::new(name: &str, qty: u32) -> Itemqty(&self) -> u32, the getteradd(&mut self, amount: u32)remove(&mut self, amount: u32) -> Result<(), String>: if there aren't enough, returnErr(format!("only {} left", qty))and change nothing.
The tests use the item through inventory::Item.
Challenge 2
A thermostat must always target a temperature between 10.0 and 30.0. Finish the module so that invariant can't be broken: new and set clamp the value into that range (t.clamp(10.0, 30.0) does exactly that), and target() is the getter. The field stays private.
Challenge 3
main doesn't compile: it reaches into Queue's private fields. Rewrite only main so it uses the public methods Queue::new, open, count and list (don't change the module, and don't make fields public). The program should print:
opened #1
opened #2
2 tickets
#1 Login fails
#2 Slow search
Previous: use: shortcuts for long paths Next: Re-exports with pub use