Skip to content
AgileLogics
6 min read

Modules, Crates & Packages

Prerequisites: Level 1 — Beginner

In Level 1 every program lived in a single src/main.rs. Larger programs are split into modules, and Rust has three words for how code is organized:

  • A module (mod) is a named scope for functions, types and other modules. It controls what is visible from outside.
  • A crate is one unit of compilation. A binary crate has a main function and its root file is src/main.rs. A library crate has no main, is meant to be used by other code, and its root file is src/lib.rs.
  • A package is what cargo new creates: a Cargo.toml plus one or more crates. A package can contain at most one library crate and any number of binary crates.

Everything inside a module is private by default. Mark an item pub to make it visible to the parent module and beyond. Struct fields are private even when the struct itself is pub, so each field needs its own pub. Enum variants are public whenever the enum is.

Syntax

mod network {                  // define a module inline
    pub fn connect() {}        // visible outside `network`
    fn retry() {}              // private to `network` and its children
}

use crate::network::connect;   // bring a path into scope
// paths: crate:: (from the crate root), self:: (this module), super:: (parent)

Example

mod geometry {
    pub mod shapes {
        #[derive(Debug)]
        pub struct Rectangle {
            pub width: u32,
            pub height: u32,
        }

        impl Rectangle {
            pub fn area(&self) -> u32 {
                self.width * self.height
            }
        }

        pub fn square(side: u32) -> Rectangle {
            Rectangle { width: side, height: side }
        }
    }

    pub fn describe(r: &shapes::Rectangle) -> String {
        format!("{}x{} (area {})", r.width, r.height, area_of(r))
    }

    // Private helper: only code inside `geometry` can call it.
    fn area_of(r: &shapes::Rectangle) -> u32 {
        r.area()
    }
}

use geometry::shapes::{self, Rectangle};

fn main() {
    let r = Rectangle { width: 3, height: 4 };
    let s = shapes::square(5);
    println!("{}", geometry::describe(&r));
    println!("{}", geometry::describe(&s));
}

Expected output

3x4 (area 12)
5x5 (area 25)

Moving modules into files

Inline modules are fine for small programs. For real projects, replace the body with a semicolon and Cargo looks for the code in a file:

src/
├── main.rs             mod geometry;
├── geometry.rs         pub mod shapes;  + describe() and area_of()
└── geometry/
    └── shapes.rs       Rectangle, square()

src/geometry.rs:

pub mod shapes;

pub fn describe(r: &shapes::Rectangle) -> String {
    format!("{}x{} (area {})", r.width, r.height, area_of(r))
}

fn area_of(r: &shapes::Rectangle) -> u32 {
    r.area()
}

src/geometry/shapes.rs contains exactly what was inside pub mod shapes { ... }, and src/main.rs starts with mod geometry;. The output is unchanged. The older layout src/geometry/mod.rs also works, but src/geometry.rs is the usual style today.

A mod declaration appears once, in the parent module. Other files reach the module with use crate::geometry::... — never with a second mod.

lib.rs and main.rs together

A common layout is a package with both crate roots: the logic lives in the library (src/lib.rs) and src/main.rs is a thin wrapper. The binary uses the library by the package name, as if it were an external dependency:

// src/lib.rs in a package named `hello`
pub fn greet(name: &str) -> String {
    format!("Hello, {name}!")
}
// src/main.rs in the same package
use hello::greet;

fn main() {
    println!("{}", greet("Ada"));
}

Common mistakes

  • Calling a private function from outside its module, such as geometry::area_of(&r) in main. The compiler reports error[E0603]: function `area_of` is private.
  • Making a struct pub but not its fields, then building it outside the module: error[E0451]: field `width` of struct `Rectangle` is private.
  • Creating src/geometry.rs but forgetting mod geometry; in main.rs. A file is not part of the crate until some module declares it, so use geometry::... fails with error[E0433]: failed to resolve: use of undeclared crate or module `geometry` .
  • Writing mod geometry; again in a second file to "import" it. That tells Rust to look for another file relative to that module. Use use instead.

Practice

In a package named calc, create src/lib.rs with a public module math containing a public function double(n: i32) -> i32. Call it from src/main.rs to print 42.

Show solution

src/lib.rs:

pub mod math {
    pub fn double(n: i32) -> i32 {
        n * 2
    }
}

src/main.rs:

use calc::math::double;

fn main() {
    println!("{}", double(21));
}