Step 1 of 6 · Reading · ~4 min
Learn
Modules and Threads
Modules and Visibility
You will be able to put code into modules, choose exactly which names each module shows to the rest of the program, and use private fields to make a type that cannot be built in an invalid state.
Every program so far sat at the top level of one file. A module is a named namespace inside the crate (the unit the compiler builds); this lesson is about what a module hides and what it exposes.
Privacy is the default
mod name { ... } declares a module. Everything in it is private: visible inside that module and its children, never to the parent. pub opens an item to the outside. A struct needs two decisions, because each field has its own visibility:
mod thermo {
pub struct Celsius { degrees: f64 } // the type is pub, the field is not
impl Celsius {
pub fn new(degrees: f64) -> Result<Celsius, String> {
if degrees < -273.15 {
Err(format!("{} is below absolute zero", degrees))
} else {
Ok(Celsius { degrees })
}
}
pub fn degrees(&self) -> f64 { self.degrees }
pub fn kelvin(&self) -> f64 { convert::to_kelvin(self.degrees) }
}
mod convert { // private module
pub(super) fn to_kelvin(c: f64) -> f64 { c + 273.15 }
}
}
fn main() {
let t = thermo::Celsius::new(21.5).unwrap();
println!("{} {:.2}", t.degrees(), t.kelvin()); // 21.5 294.65
if let Err(e) = thermo::Celsius::new(-300.0) {
println!("{}", e); // -300 is below absolute zero
}
}
Because degrees is private, outside code cannot write Celsius { degrees: -500.0 }: the only way to get a Celsius is new, so every Celsius in the program is valid. The compiler rejects the shortcuts:
thermo::Celsius { degrees: -500.0 } error[E0451]: field `degrees` of struct `thermo::Celsius` is private
t.degrees error[E0616]: field `degrees` of struct `thermo::Celsius` is private
thermo::convert::to_kelvin(0.0) error[E0603]: module `convert` is private
The visibility levels
| Written | Visible in |
|---|---|
| (nothing) | this module and its children |
pub(super) | the parent module (and everything inside it) |
pub(in crate::shop) | the named ancestor module |
pub(crate) | the whole crate |
pub | everywhere the module itself is reachable |
pub only helps if every module on the path is reachable too.
Paths and use
A path names an item: crate:: starts at the crate root, self:: at the current module, super:: at the parent. use brings a name into scope:
mod shop {
pub mod stock {
pub fn sold() -> u32 { super::tally() } // child may call the parent's private fn
}
fn tally() -> u32 { 3 }
pub use self::stock::sold as sold_today; // re-export under a new name
}
use shop::stock::{self, sold}; // braces; `self` imports `stock` itself
use shop::sold_today as today; // `as` renames
pub use re-exports, so callers never learn the internal layout. A glob, use shop::stock::*;, imports every public name but hides where names come from. The grader compiles the 2015 edition, where a use path starts at the crate root, so inside a nested module write use self::..., use super::... or use crate::....
Files and crates (text only)
The grader compiles one file, so you write modules inline. In a real project mod booking; loads the body from src/booking.rs (or src/booking/mod.rs); a mod x; inside it looks in src/booking/x.rs. A crate is one binary or library, and Cargo.toml lists its dependencies. A #[cfg(test)] mod tests { use super::*; ... } is an ordinary child module, compiled only by cargo test.
Common mistakes
- Making fields
pub"to get it compiling". Thenmaincan build an invalid value and the guard innewprotects nothing. Add a method instead. - Writing
pubon a function inside a private module and expecting outside access.thermo::convert::to_kelvinstays unreachable becauseconvertis private: E0603. - Using
use inner::f;in a nested module. On the 2015 edition this searches from the crate root and fails with E0432; writeuse self::inner::f;. - Treating
pub(super)aspub. Only the parent and its contents can call it.
Your exercise
Build mod booking for a seat map: a Plane with private fields, a Result-returning new, an error enum with Display, and a nested seat_code module whose parse only booking can call. The starter already contains main. Seat codes are a column letter and a 1-based row number that can have several digits, so check both ends of both ranges.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…