RustArena

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

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:

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