4 minute read

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 package and crate mean the same thing.
  • It is hard to tell when src/main.rs and src/lib.rs should exist together.
  • Once code is split into multiple files, the roles of mod, use, and pub can 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.

Verified Facts

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:

  1. Cargo.toml: check the package name and dependencies.
  2. src/main.rs: check where execution starts.
  3. src/lib.rs: check which reusable modules the crate exposes.
  4. 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)
  • Directly confirmed result: when I ran the main.rs example in a temporary Cargo project with the same structure as the post, the output was:
cargo run
  • Observed output:
sum = 30
diff = 15
  • 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

  • My interpretation is that the most important early distinction is this: a package is the Cargo-managed bundle, while a crate is the compilation unit.
  • Opinion: once a function looks reusable, moving it out of main.rs and into a library module usually makes testing and future refactoring easier.
  • Opinion: pub should 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.rs split often becomes easier to maintain.
  • Package names with hyphens introduce additional crate-name details in code, but this post keeps the example intentionally simple.

References

댓글남기기