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.rsstarts a binary crate,src/lib.rsstarts a library crate. - module (
mod): A logical grouping inside a crate. Declare it inline with amod {}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 namedauth.- 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 allowuser.rsplususer/profile.rsas an alternative layout. pub mod profile;both includesprofile.rsand 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 appto build a specific crate, keeping module boundaries crisp.
Practice Exercises
- Create
auth,user, andclimodules, then useuseimports to keep call sites clean. - Compare
pub(crate)andpubby deciding which functions deserve external visibility. - Add a
src/lib.rslibrary crate and call it fromsrc/main.rswithuse my_crate::....
Completion Checklist
- Can explain the relationships between crates, packages, and modules.
- Have wired up file-based modules yourself.
- Have combined
usewith 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.
💬 댓글
이 글에 대한 의견을 남겨주세요