[Rust Series Part 12] Understanding Modules and Package Structure

한국어 버전

Once you move past part 10, projects balloon quickly. When every function and struct lives in a single file, tracking ownership rules becomes painful. Rust's module and package system is designed to carve large codebases into digestible pieces, so let's practice that split.

Big Picture: crate, package, module

  • package: The top-level unit anchored by Cargo.toml. It can contain one or more crates. Most beginner projects ship with just one binary crate.
  • crate: What the compiler builds independently. src/main.rs starts a binary crate, src/lib.rs starts a library crate.
  • module (mod): A logical grouping inside a crate. Declare it inline with a mod {} block or pull it out into files.

A mental picture: a package holds one or more crates, and each crate nests many modules.

Declaring Modules Inline

Start small by placing mod blocks inside src/main.rs.

mod auth {
    pub fn login(user: &str) {
        println!("{} logged in", user);
    }
}

fn main() {
    auth::login("yuna");
}
  • mod auth { ... } defines a module named auth.
  • Functions intended for use outside the module must be pub.
  • Use :: to walk the module path.

Splitting Modules into Files

As files grow, declare mod foo; and Rust will look for foo.rs or foo/mod.rs.

src/
├── main.rs
├── auth.rs
└── user/
    ├── mod.rs
    └── profile.rs

main.rs pulls modules in like this:

mod auth;
mod user;

fn main() {
    auth::login("yuna");
    user::profile::show("yuna");
}

Then user/mod.rs wires up its children:

pub mod profile;

pub fn list() {
    println!("User list");
}
  • Directory-style modules historically use mod.rs, though newer editions allow user.rs plus user/profile.rs as an alternative layout.
  • pub mod profile; both includes profile.rs and re-exports it so outside modules can reach it.

In other words, when user/mod.rs contains pub mod profile;, Rust expects a sibling file user/profile.rs.

Shortening Paths with use

Avoid repeating long paths by importing them.

use crate::auth::login;
use crate::user::profile::show;

fn main() {
    login("yuna");
    show("yuna");
}
  • crate:: points to the crate root.
  • super:: climbs one module up, self:: refers to the current module.
  • Use braces like use crate::user::{profile, list}; to import multiple items from the same path.

Remember use only shortens names; it does not re-export them. Use pub use to expose imports further up the tree.

Designing Visibility

Rust's module system shines when you deliberately control what becomes public.

  • pub(crate) exposes items only to the current crate.
  • pub(super) exposes to the parent module.
  • Keep implementation details (database adapters, low-level helpers) private and expose only the API surface with pub.

Quick Tour of Cargo Workspaces

Large projects often stitch multiple crates together with a workspace. Here's just the taste.

[workspace]
  • Declare the workspace in the root Cargo.toml, while every member crate keeps its own manifest.
  • Run cargo build -p app to build a specific crate, keeping module boundaries crisp.

Practice in CodeSandbox

The sandbox below uses CodeSandbox's Rust starter. Move the main code into src/main.rs, then compare cargo check and cargo run so you can read the compiler feedback beside the final output.

Live Practice

Rust Practice Sandbox

CodeSandbox

Run the starter project in CodeSandbox, compare it with the lesson code, and keep experimenting.

Rust startercargoterminal
  1. Fork the starter and open src/main.rs
  2. Paste the lesson code and run cargo check plus cargo run in order
  3. Change types, values, or borrowing flow and compare the compiler feedback with the output

Rust practice here is mainly terminal-driven rather than browser-preview driven. Lessons that need multiple files or extra crates may require a bit more setup inside the starter.

Practice Exercises

  1. Create auth, user, and cli modules, then use use imports to keep call sites clean.
  2. Compare pub(crate) and pub by deciding which functions deserve external visibility.
  3. Add a src/lib.rs library crate and call it from src/main.rs with use my_crate::....

Completion Checklist

  • Can explain the relationships between crates, packages, and modules.
  • Have wired up file-based modules yourself.
  • Have combined use with visibility modifiers to shape an API surface.

Next time we'll sit higher up the abstraction ladder with generics and traits so repeated logic lives in the type system instead of copy-pasted functions.

💬 댓글

이 글에 대한 의견을 남겨주세요