BBuzzard

A module groups items under a name. mod name { ... } defines one inline. mod name; loads it from a file: name.rs next to the current file, or name/mod.rs. A module's own submodules live in a folder named after it (shapes.rs plus shapes/circle.rs in this lesson). Paths name items: crate:: starts at the crate root, super:: goes to the parent, and self:: is the current module.

Everything is private by default. pub makes an item visible to the parent module and beyond; pub(crate) limits it to this crate and pub(super) to the parent. Struct fields are private unless marked pub, which is how a type protects its invariants. use brings a path into scope, as renames it, and pub use re-exports it under a shorter path.

#[cfg(...)] compiles an item only when a condition holds (a platform, test builds, a Cargo feature), and cfg!(...) is the same test as a bool. include_str! and include_bytes! embed a file into the binary at compile time.

This repository is one Cargo package with many [[bin]] targets, one per lesson. cargo run --bin NAME builds and runs one of them; cargo check --bin NAME only type-checks it, which is faster.

Modules from files

mod greet;
mod shapes;

use std::collections::HashMap as Map;
use std::fmt::{self, Display};
use std::io::{self, Write};

fn demo_file_modules() {
    println!("-- file modules --");

    println!("{}", greet::hello("Ada"));
    let sq = shapes::Square { side: 1.5 };
    let c = shapes::Circle { r: 1.0 }; // via the pub use re-export
    let c2 = shapes::circle::Circle { r: 2.0 }; // via the full path
    println!("square {} circle {} circle2 {}", sq.area(), c.area(), c2.area());

    // shapes::helpers::round2(1.0);
    // error[E0603]: module `helpers` is private
}

Visibility and private fields

mod bank {
    pub struct Account {
        pub owner: String,
        balance: i64, // private: only code in `bank` can touch it
    }

    impl Account {
        pub fn open(owner: &str) -> Self {
            Account { owner: owner.into(), balance: 0 }
        }

        pub fn deposit(&mut self, amount: i64) {
            if amount > 0 {
                self.balance += amount;
                audit::record(amount);
            }
        }

        pub fn balance(&self) -> i64 {
            self.balance
        }
    }

    pub(crate) fn bank_name() -> &'static str {
        "Rust Bank"
    }

    mod audit {
        use std::sync::atomic::{AtomicI64, Ordering};
        static TOTAL: AtomicI64 = AtomicI64::new(0);

        pub(super) fn record(amount: i64) {
            TOTAL.fetch_add(amount, Ordering::Relaxed);
        }

        pub(in crate::bank) fn total() -> i64 {
            TOTAL.load(Ordering::Relaxed)
        }
    }

    pub fn audited_total() -> i64 {
        self::audit::total()
    }
}

fn demo_visibility() {
    println!("-- visibility --");

    let mut a = bank::Account::open("ada");
    a.deposit(50);
    a.deposit(-10); // rejected by the method, which is the point of a private field
    println!("{} has {} at {}", a.owner, a.balance(), bank::bank_name());
    println!("audited {}", bank::audited_total());

    // a.balance = 1_000_000;
    // error[E0616]: field `balance` of struct `Account` is private
    // bank::Account { owner: "x".into(), balance: 5 };
    // error[E0451]: field `balance` of struct `Account` is private
}

Paths: crate::, super::, self::, and use

mod geometry {
    pub mod units {
        pub const CM_PER_INCH: f64 = 2.54;
    }

    pub fn to_cm(inches: f64) -> f64 {
        inches * units::CM_PER_INCH
    }

    pub mod nested {
        pub fn from_root() -> f64 {
            crate::geometry::to_cm(1.0) + super::units::CM_PER_INCH
        }
    }
}

fn demo_paths() {
    println!("-- paths and use --");

    use geometry::units::CM_PER_INCH;
    use geometry::{nested, to_cm};
    println!("{} {} {}", to_cm(10.0), CM_PER_INCH, nested::from_root());

    let mut m: Map<&str, i32> = Map::new(); // HashMap renamed with `as`
    m.insert("n", 1);
    writeln!(io::stdout(), "{m:?}").unwrap(); // io and Write from a grouped use
}

cfg attributes and the cfg! macro

struct Version(u32, u32);

impl Display for Version {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "v{}.{}", self.0, self.1)
    }
}

#[cfg(unix)]
fn platform() -> &'static str {
    "unix"
}

Embedding files at compile time

#[cfg(not(unix))]
fn platform() -> &'static str {
    "not unix"
}

fn demo_cfg() {
    println!("-- cfg --");

    println!("platform {} os {}", platform(), std::env::consts::OS);
    println!("debug build? {}", cfg!(debug_assertions));
    println!("64-bit? {}", cfg!(target_pointer_width = "64"));

    if cfg!(target_os = "macos") {
        println!("hello from macOS");
    } else if cfg!(target_os = "linux") {
        println!("hello from Linux");
    }

    println!("version {}", Version(1, 2));
}

fn demo_include() {
    println!("-- include_str / include_bytes --");

    let note: &'static str = include_str!("note.txt");
    let bytes: &'static [u8] = include_bytes!("note.txt");
    println!("{:?} ({} bytes)", note.trim(), bytes.len());
}

Compile-time information about the crate

fn demo_crate_info() {
    println!("-- crate info --");

    println!("module_path {}", module_path!());
    println!("file {} line {}", file!(), line!());

    // env! reads a variable at compile time. Cargo sets CARGO_PKG_* for every build;
    // option_env! returns None instead of failing when the variable is missing.
    println!(
        "package {:?} version {:?}",
        option_env!("CARGO_PKG_NAME"),
        option_env!("CARGO_PKG_VERSION")
    );
    println!("try: cargo check --bin 53_modules (type-check only)");
}
$ cargo run --bin 53_modules
-- file modules --
hello Ada
square 2.25 circle 3.14 circle2 12.57
-- visibility --
ada has 50 at Rust Bank
audited 50
-- paths and use --
25.4 2.54 5.08
{"n": 1}
-- cfg --
platform unix os linux
debug build? true
64-bit? true
hello from Linux
version v1.2
-- include_str / include_bytes --
"embedded note" (14 bytes)
-- crate info --
module_path 53_modules
file 53_modules/main.rs line 184
package Some("learn") version Some("0.1.0")
try: cargo check --bin 53_modules (type-check only)

53_modules/greet.rs

pub fn hello(name: &str) -> String {
    format!("hello {name}")
}

53_modules/note.txt

embedded note

53_modules/shapes/circle.rs

// shapes::circle, declared with `pub mod circle;` in shapes.rs.

pub struct Circle {
    pub r: f64,
}

impl Circle {
    pub fn area(&self) -> f64 {
        // super:: is the parent module (shapes), which can see its private helpers.
        super::helpers::round2(std::f64::consts::PI * self.r * self.r)
    }
}

53_modules/shapes.rs

// The shapes module. Because this file is shapes.rs, its submodules live in shapes/.

pub mod circle;

mod helpers {
    // Private to shapes: only code inside shapes (and its children) can call it.
    pub fn round2(x: f64) -> f64 {
        (x * 100.0).round() / 100.0
    }
}

pub struct Square {
    pub side: f64,
}

impl Square {
    pub fn area(&self) -> f64 {
        helpers::round2(self.side * self.side)
    }
}

// Re-export so callers can write shapes::Circle instead of shapes::circle::Circle.
pub use circle::Circle;

Full program

53_modules/main.rs · 215 lines
// 53. Modules, use, visibility, cfg, include_str, and Cargo
//
// A module groups items under a name. mod name { ... } defines one inline. mod name;
// loads it from a file: name.rs next to the current file, or name/mod.rs. A module's
// own submodules live in a folder named after it (shapes.rs plus shapes/circle.rs in
// this lesson). Paths name items: crate:: starts at the crate root, super:: goes to the
// parent, and self:: is the current module.
//
// Everything is private by default. pub makes an item visible to the parent module and
// beyond; pub(crate) limits it to this crate and pub(super) to the parent. Struct fields
// are private unless marked pub, which is how a type protects its invariants. use brings
// a path into scope, as renames it, and pub use re-exports it under a shorter path.
//
// #[cfg(...)] compiles an item only when a condition holds (a platform, test builds, a
// Cargo feature), and cfg!(...) is the same test as a bool. include_str! and
// include_bytes! embed a file into the binary at compile time.
//
// This repository is one Cargo package with many [[bin]] targets, one per lesson.
// cargo run --bin NAME builds and runs one of them; cargo check --bin NAME only
// type-checks it, which is faster.
//
// Run: cargo run --bin 53_modules

mod greet;
mod shapes;

use std::collections::HashMap as Map;
use std::fmt::{self, Display};
use std::io::{self, Write};

// Example 1: modules from files.
fn demo_file_modules() {
    println!("-- file modules --");

    println!("{}", greet::hello("Ada"));
    let sq = shapes::Square { side: 1.5 };
    let c = shapes::Circle { r: 1.0 }; // via the pub use re-export
    let c2 = shapes::circle::Circle { r: 2.0 }; // via the full path
    println!("square {} circle {} circle2 {}", sq.area(), c.area(), c2.area());

    // shapes::helpers::round2(1.0);
    // error[E0603]: module `helpers` is private
}

mod bank {
    pub struct Account {
        pub owner: String,
        balance: i64, // private: only code in `bank` can touch it
    }

    impl Account {
        pub fn open(owner: &str) -> Self {
            Account { owner: owner.into(), balance: 0 }
        }

        pub fn deposit(&mut self, amount: i64) {
            if amount > 0 {
                self.balance += amount;
                audit::record(amount);
            }
        }

        pub fn balance(&self) -> i64 {
            self.balance
        }
    }

    pub(crate) fn bank_name() -> &'static str {
        "Rust Bank"
    }

    mod audit {
        use std::sync::atomic::{AtomicI64, Ordering};
        static TOTAL: AtomicI64 = AtomicI64::new(0);

        pub(super) fn record(amount: i64) {
            TOTAL.fetch_add(amount, Ordering::Relaxed);
        }

        pub(in crate::bank) fn total() -> i64 {
            TOTAL.load(Ordering::Relaxed)
        }
    }

    pub fn audited_total() -> i64 {
        self::audit::total()
    }
}

// Example 2: visibility and private fields.
fn demo_visibility() {
    println!("-- visibility --");

    let mut a = bank::Account::open("ada");
    a.deposit(50);
    a.deposit(-10); // rejected by the method, which is the point of a private field
    println!("{} has {} at {}", a.owner, a.balance(), bank::bank_name());
    println!("audited {}", bank::audited_total());

    // a.balance = 1_000_000;
    // error[E0616]: field `balance` of struct `Account` is private
    // bank::Account { owner: "x".into(), balance: 5 };
    // error[E0451]: field `balance` of struct `Account` is private
}

mod geometry {
    pub mod units {
        pub const CM_PER_INCH: f64 = 2.54;
    }

    pub fn to_cm(inches: f64) -> f64 {
        inches * units::CM_PER_INCH
    }

    pub mod nested {
        pub fn from_root() -> f64 {
            crate::geometry::to_cm(1.0) + super::units::CM_PER_INCH
        }
    }
}

// Example 3: paths: crate::, super::, self::, and use.
fn demo_paths() {
    println!("-- paths and use --");

    use geometry::units::CM_PER_INCH;
    use geometry::{nested, to_cm};
    println!("{} {} {}", to_cm(10.0), CM_PER_INCH, nested::from_root());

    let mut m: Map<&str, i32> = Map::new(); // HashMap renamed with `as`
    m.insert("n", 1);
    writeln!(io::stdout(), "{m:?}").unwrap(); // io and Write from a grouped use
}

struct Version(u32, u32);

impl Display for Version {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "v{}.{}", self.0, self.1)
    }
}

// Example 4: cfg attributes and the cfg! macro.
#[cfg(unix)]
fn platform() -> &'static str {
    "unix"
}

#[cfg(not(unix))]
fn platform() -> &'static str {
    "not unix"
}

fn demo_cfg() {
    println!("-- cfg --");

    println!("platform {} os {}", platform(), std::env::consts::OS);
    println!("debug build? {}", cfg!(debug_assertions));
    println!("64-bit? {}", cfg!(target_pointer_width = "64"));

    if cfg!(target_os = "macos") {
        println!("hello from macOS");
    } else if cfg!(target_os = "linux") {
        println!("hello from Linux");
    }

    println!("version {}", Version(1, 2));
}

// Example 5: embedding files at compile time.
fn demo_include() {
    println!("-- include_str / include_bytes --");

    let note: &'static str = include_str!("note.txt");
    let bytes: &'static [u8] = include_bytes!("note.txt");
    println!("{:?} ({} bytes)", note.trim(), bytes.len());
}

// Example 6: compile-time information about the crate.
fn demo_crate_info() {
    println!("-- crate info --");

    println!("module_path {}", module_path!());
    println!("file {} line {}", file!(), line!());

    // env! reads a variable at compile time. Cargo sets CARGO_PKG_* for every build;
    // option_env! returns None instead of failing when the variable is missing.
    println!(
        "package {:?} version {:?}",
        option_env!("CARGO_PKG_NAME"),
        option_env!("CARGO_PKG_VERSION")
    );
    println!("try: cargo check --bin 53_modules (type-check only)");
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn deposit_ignores_negative() {
        let mut a = bank::Account::open("t");
        a.deposit(-5);
        assert_eq!(a.balance(), 0);
    }
}

fn main() {
    demo_file_modules();
    demo_visibility();
    demo_paths();
    demo_cfg();
    demo_include();
    demo_crate_info();
}