Modules, Crates, and Project Structure

As a program grows past one file, you need a way to organize it — to group related code, control what's public, and pull in libraries. Rust's module system does this with a clear hierarchy and privacy-by-default, and understanding crates, modules, and paths is what lets your projects scale beyond a single main.rs. This closes Module 2.

Module 2 covered the building blocks — collections, generics, traits, closures, iterators, smart pointers. This final post covers how to organize code as it grows: Rust’s module system (crates, modules, paths, and visibility). It’s the practical knowledge that takes you from a single main.rs to a structured, maintainable project — and it rounds out the everyday Rust you need to build real programs. This post covers crates, modules, paths, use, and privacy.

Crates: the compilation unit

From the Cargo post (Module 1), a crate is Rust’s unit of compilation and distribution — a library or executable. Two kinds:

A package (what cargo new creates) contains one or more crates plus a Cargo.toml. Your dependencies (from crates.io) are library crates your package uses. So the hierarchy is: a package holds crates, a crate is the compilation unit, and — within a crate — you organize code into modules. Understanding this nesting (package → crate → modules) is the frame for everything below.

Modules: organizing within a crate

Modules organize code within a crate — grouping related items (functions, structs, traits) into a namespace and controlling their visibility. You declare a module with mod:

mod network {
    pub fn connect() { /* ... */ }      // pub = public, callable outside the module

    fn helper() { /* ... */ }           // private to the module (default)

    pub mod client {                    // nested module
        pub fn request() { /* ... */ }
    }
}

fn main() {
    network::connect();                 // path to the module's item
    network::client::request();         // path through nested modules
}

Modules form a tree — modules can nest inside modules — giving your crate a hierarchical namespace. This is how you group related code (a network module, an auth module, a db module) and keep the crate organized rather than one giant flat file. As a project grows, modules can live in separate files/directories: mod network; (without a body) tells Rust to load the module from network.rs or network/mod.rs, so your module tree maps onto your file structure. This is how you split a crate across many files while keeping a coherent module hierarchy.

Privacy: private by default

A defining Rust choice, consistent with its safety-and-explicitness philosophy: items are private by default. A function, struct, field, or module is only accessible within its own module (and descendants) unless you mark it pub (public):

Private-by-default is deliberate: it means your module’s internals are hidden unless you choose to expose them, so a module presents a controlled public interface and hides its implementation. This is encapsulation enforced by the language — you opt into what’s public, rather than everything being public unless hidden. For library crates especially, pub defines your API surface: what you mark pub is your public contract; everything else is free to change. This “expose deliberately, hide by default” is good software design, built into Rust’s module system.

Paths and use: referring to items

To use an item, you refer to it by its path through the module tree — like a filesystem path, but with :::

Writing full paths everywhere is verbose, so the use keyword brings an item into scope so you can refer to it by a short name:

use std::collections::HashMap;         // now write HashMap, not std::collections::HashMap
use network::client::request;

fn main() {
    let map = HashMap::new();          // short name, thanks to `use`
    request();
}

use is what you saw in earlier posts (use std::collections::HashMap;) — it imports a path into scope. You can use items from the standard library, from dependency crates, or from your own modules. Idiomatically, you use the parent module for functions (use network::client; then client::request()) and the item directly for types (use ...::HashMap;), which keeps call sites readable while showing where functions come from. pub use (re-exporting) lets a crate expose items from deep in its tree at a convenient top-level path — shaping a clean public API.

Structuring a real project

Putting it together, a growing Rust project is organized like this:

my_app/
├── Cargo.toml           # package manifest + dependencies
└── src/
    ├── main.rs          # binary crate root (or lib.rs for a library)
    ├── network.rs       # a module (mod network;)
    ├── auth/            # a module with submodules
    │   ├── mod.rs       #   (or auth.rs) — the auth module
    │   └── tokens.rs    #   a submodule (mod tokens;)
    └── db.rs            # another module

This is how Rust projects scale from a single file to large, maintainable codebases: a clear package → crate → module hierarchy, privacy by default defining clean interfaces, and paths/use tying it together. It’s not glamorous, but it’s what makes real Rust programs organized rather than sprawling.

Module 2 complete

This closes Module 2. You now have the everyday Rust toolkit built on Module 1’s foundations: collections (Vec/String/HashMap), generics (code over many types, zero-cost), traits (shared behavior, Rust’s abstraction core), trait objects (dynamic dispatch for heterogeneous/runtime cases), closures (captured environment, ownership-aware), iterators (lazy, composable, zero-cost), smart pointers (Box/Rc/RefCell for heap, sharing, interior mutability), and now the module system to organize it all. Together with Module 1, you can now write real, idiomatic, well-organized Rust programs — abstractions that are safe (ownership) and free (zero-cost), composed into structured projects. Later modules build further (concurrency, async, testing, macros, and more), but you now command the core of the language: the safety of Module 1 and the abstraction and organization of Module 2.

Key takeaways

Further reading

Sources & References