Skip to content
Lesson 8 of 9

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

WrittenVisible 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
pubeverywhere 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". Then main can build an invalid value and the guard in new protects nothing. Add a method instead.
  • Writing pub on a function inside a private module and expecting outside access. thermo::convert::to_kelvin stays unreachable because convert is private: E0603.
  • Using use inner::f; in a nested module. On the 2015 edition this searches from the crate root and fails with E0432; write use self::inner::f;.
  • Treating pub(super) as pub. 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.

Up nextThreads, Channels and Shared StateModules and Threads

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…