Rust 09. Crates, Packages, and Project Layout
Summary
Once you move past syntax basics in Rust, the next common point of confusion is project structure. If Cargo.toml, src/main.rs, src/lib.rs, mod, use, and pub are still blurred together, even a small project quickly becomes hard to organize.
This post focuses on the relationship between packages and crates, the difference between a binary crate and a library crate, and how main.rs, lib.rs, mod, use, and pub fit together. The practical beginner rule is simple: treat main.rs as the entry point, move reusable logic into lib.rs, and expose only the items that really need to be public.
Document Information
- Written on: 2026-04-15
- Verification date: 2026-04-16
- Document type: tutorial
- Test environment: Windows 11 Pro, Windows PowerShell, Cargo CLI examples
- Test version: rustc 1.94.0, cargo 1.94.0
- Source grade: only official documentation is used.
- Note: this post stays focused on the basic structure of a single Cargo package and intentionally leaves out broader topics such as workspaces and features.
Problem Definition
After learning basic module syntax, beginners often get stuck at the next step.
- It is not obvious whether
packageandcratemean the same thing. - It is hard to tell when
src/main.rsandsrc/lib.rsshould exist together. - Once code is split into multiple files, the roles of
mod,use, andpubcan feel vague.
This post stays intentionally narrow. It explains how to read and split one Cargo project, without going into wider topics like workspaces, publishing, features, or path dependencies.
How to read this post: separate the bundle Cargo manages from the code unit Rust compiles. At the beginner stage, it is possible to put everything in main.rs, but it is more useful to learn how the entry point and reusable logic can be separated.
Verified Facts
- According to the official Rust Book, a Cargo package is the unit described by
Cargo.toml, and a package can contain at most one library crate and any number of binary crates. Evidence: Packages and Crates Meaning: a package is the project bundle Cargo manages, while a crate is a compilation unit. - According to the official docs, a binary crate has a
mainfunction as its entry point, while a library crate exposes reusable functionality. Evidence: Packages and Crates Meaning: execution belongs in the binary crate, and reusable or testable logic is usually better placed in the library crate. - According to the official docs,
moddefines modules,usebrings a path into the current scope, andpubcontrols visibility. Evidence: Defining Modules to Control Scope and Privacy, Paths for Referring to an Item in the Module Tree, Bringing Paths into Scope with the use Keyword Meaning:mod,use, andpubdo not do the same job.modconnects a module,useshortens access to a path, andpubdefines what crosses the API boundary. - According to the official docs, modules can be moved into separate files and connected from the crate root. Evidence: Separating Modules into Different Files Meaning: splitting a file is not enough by itself. The crate root still has to connect that module into the module tree.
A useful beginner mental model is this layout:
rust-layout-demo/
Cargo.toml
src/
main.rs
lib.rs
math.rs
It helps to read that structure in this order:
Cargo.toml: check the package name and dependencies.src/main.rs: check where execution starts.src/lib.rs: check which reusable modules the crate exposes.src/math.rs: check where the actual feature logic lives.
For example, lib.rs can expose a module like this:
pub mod math;
Then math.rs can hold the reusable functions.
pub fn add(left: i32, right: i32) -> i32 {
left + right
}
pub fn subtract(left: i32, right: i32) -> i32 {
left - right
}
And main.rs can stay focused on wiring execution together.
use rust_layout_demo::math;
fn main() {
let sum = math::add(10, 20);
let diff = math::subtract(20, 5);
println!("sum = {}", sum);
println!("diff = {}", diff);
}
The important beginner takeaway is that execution starts in main.rs, but the real logic does not need to stay there. Once you move reusable code into lib.rs and its modules, later topics like testing, file I/O, CLI tools, and small projects become much easier to structure.
Directly Confirmed Results
- Directly confirmed result: the Rust toolchain versions available in the current writing environment were:
rustc --version
cargo --version
- Observed output:
rustc 1.94.0 (4a4ef493e 2026-03-02)
cargo 1.94.0 (85eff7c80 2026-01-15)
-
How to read this: these are the tool versions used to run the structure example. The project layout rules are stable for this beginner use case, but warning text and command output can still vary by version.
-
Directly confirmed result: when I ran the
main.rsexample in a temporary Cargo project with the same structure as the post, the output was:
cargo run
- Observed output:
sum = 30
diff = 15
-
How to read this:
main.rsonly wires the run together, while the actual arithmetic functions come from the library crate module. This output confirms thatlib.rs,math.rs, anduse rust_layout_demo::mathare connected correctly. -
Limitation of direct reproduction: I reproduced the representative example in a temporary Cargo project, but I did not add a separate example project to this repository.
Interpretation / Opinion
- Key decision at this stage: the most important early distinction is this: a package is the Cargo-managed bundle, while a crate is the compilation unit.
- Decision rule: keep execution wiring in
main.rs, and move reusable or testable logic intolib.rsand its modules. - Interpretation:
pubshould be treated as an API boundary, not as a convenience switch. Keeping most items private by default tends to produce cleaner project structure.
Limits and Exceptions
- This post explains a single-package project and does not cover Cargo workspaces.
- It does not go into finer visibility details such as
pub(crate),super, or deeper nested module layouts. - You can build a Rust program without a library crate. Still, once the project grows, the
lib.rssplit often becomes easier to maintain. - Package names with hyphens introduce additional crate-name details in code, but this post keeps the example intentionally simple.
- Remaining questions after this post include workspaces, feature flags, publishing, and path dependencies. Those belong in a project-operations layer rather than this beginner layout article.
댓글남기기