Skip to content

Packages and crates

Level: 101 · for newcomers

One line: A package is a folder with a Cargo.toml; a crate is one unit rustc compiles — a library or a binary — and a package holds at most one library crate and any number of binary crates, each with its own module tree rooted at one file. Every binary sees the library by the package's name and the other binaries not at all.

Word Lives in Named by
package Cargo.toml and the folder around it name = "…" in the manifest
library crate src/lib.rs, the crate root the package's name, - turned into _
binary crate src/main.rs, or one file each under src/bin/ src/main.rs takes the package's name; a file under src/bin/ takes its own
module mod name; or mod name { … } inside a crate the mod line — see Modules and visibility

One package with all three kinds of target:

demo/two-crates/Cargo.toml
[package]
name = "pg-pkg-two-crates"
version = "0.1.0"
edition = "2024"
publish = false
demo/two-crates/src/lib.rs
//! The library crate. Its name is the package's, with `-` turned into `_`,
//! because `pg-pkg-two-crates` is not a Rust identifier and `pg_pkg_two_crates` is.

mod greeting {
    pub fn hello(from: &str) -> String {
        format!("hello from {from}, through the library crate {}", module_path!().split("::").next().unwrap_or("?"))
    }
}

pub use greeting::hello;
demo/two-crates/src/main.rs
//! The binary crate that shares the package's name. It sees the library by
//! that name, not by `crate::`, which here means main.rs itself.

use pg_pkg_two_crates::hello;

fn main() {
    println!("{}", hello("src/main.rs"));
    println!("this crate is called {}", module_path!());
}
demo/two-crates/src/bin/report.rs
//! A second binary crate, from src/bin/. Same library, same spelling.

use pg_pkg_two_crates::hello;

fn main() {
    println!("{}", hello("src/bin/report.rs"));
    println!("this crate is called {}", module_path!());
}

Verified output of cargo run -q -p pg-pkg-two-crates --bin pg-pkg-two-crates — declared in cargo_runs.toml and regenerated by tools/run_cargo_demos.py, never hand-typed.

hello from src/main.rs, through the library crate pg_pkg_two_crates
this crate is called pg_pkg_two_crates

Verified output of cargo run -q -p pg-pkg-two-crates --bin report — declared in cargo_runs.toml and regenerated by tools/run_cargo_demos.py, never hand-typed.

hello from src/bin/report.rs, through the library crate pg_pkg_two_crates
this crate is called report

cargo metadata is how Cargo itself lists what it will build. Asked for the committed package, then for a fresh cargo new, it answers the questions this page exists for:

Verified output of sh targets.sh — declared in cargo_runs.toml and regenerated by tools/run_cargo_demos.py, never hand-typed.

1. The committed package pg-pkg-two-crates: kind, target name, file
   lib  pg_pkg_two_crates    src/lib.rs
   bin  pg-pkg-two-crates    src/main.rs
   bin  report               src/bin/report.rs

2. cargo new my_package, on this toolchain
edition = "2024"
   bin  my_package           src/main.rs

3. Listing 6.2: bin/my_binary.rs at the package root, beside src/
   bin  my_package           src/main.rs
Hello, world!

4. The same file moved to src/bin/
   bin  my_binary            src/bin/my_binary.rs
   bin  my_package           src/main.rs

5. rustc alone: one file, one crate, named after the file
main
my_binary

The library's name has an underscore

The package is pg-pkg-two-crates. The library crate is pg_pkg_two_crates, because a crate name is a Rust identifier and - is not allowed in one, so use pg_pkg_two_crates::hello; is what main.rs and report.rs write. Section 1 of the key shows both spellings side by side: the lib target is named with the underscore, the bin target that main.rs makes keeps the dash, since an executable's file name can hold one.

Paths inside a crate, and the name from outside

Inside a crate every path starts from one of three places: crate:: for the root, self:: for the current module, super:: for its parent. main.rs and lib.rs are two crates, so crate:: in main.rs is main.rs — the library is reached by its name, pg_pkg_two_crates::hello, the way any dependency is. The kata at the end of this page walks five places in one package and says where crate:: points from each; One module per file has the same fact from the module side.

What rustc file.rs makes

One crate and no package. rustc --print crate-name on src/main.rs answers main, and on src/bin/my_binary.rs it answers my_binary — section 5 of the key — because without Cargo the crate is named after the file, and CARGO_PKG_NAME is not set at all. That is how every example in this library is compiled, and Running a scratch program is the page for it.

The trap: shared code in main.rs

Each file under src/bin/ is a crate of its own, so a module declared in main.rs does not exist for it:

demo/bin_sees_no_main/src/main.rs
//! Shared code put in main.rs, where the other binary cannot see it.

mod shared {
    pub fn greeting() -> &'static str {
        "hello"
    }
}

fn main() {
    println!("{}", shared::greeting());
}
demo/bin_sees_no_main/src/bin/other.rs
//! `crate::` here is other.rs, a crate with no `shared` module in it.

fn main() {
    println!("{}", crate::shared::greeting());
}

Verified output of cargo build -q -p pg_pkg_bin_sees_no_main, which fails on purpose — declared in cargo_runs.toml and regenerated by tools/run_cargo_demos.py, never hand-typed.

error[E0433]: cannot find `shared` in `crate`
 --> bin_sees_no_main/src/bin/other.rs:4:27
  |
4 |     println!("{}", crate::shared::greeting());
  |                           ^^^^^^ could not find `shared` in the crate root

For more information about this error, try `rustc --explain E0433`.
error: could not compile `pg_pkg_bin_sees_no_main` (bin "other") due to 1 previous error

crate in other.rs is other.rs. Naming the package instead, use pg_pkg_bin_sees_no_main::shared;, fails the same way, since there is no library crate for that name to mean — rustc even suggests cargo add-ing your own package. The fix is src/lib.rs: put shared there, and main.rs and other.rs both use it by the package's name.

The book's bin/ folder, and what cargo new writes today

Rust: The Practical Guide (Nouman Azam, Rheinwerk 2025) draws its package as Cargo.toml, src/main.rs and a bin/my_binary.rs beside src/, and its manifest says edition = "2021". Sections 2 to 4 of the key measure both on this toolchain. cargo new on rustc 1.98.0 writes edition = "2024". A bin/ folder at the package root is not a target: with the book's tree, cargo metadata lists the one binary main.rs makes, the file in bin/ is never compiled, and cargo run prints Hello, world! as if it were not there. Moved to src/bin/, the file becomes a second binary target, and from then on cargo run needs --bin or a default-run line. The rest of that chapter is checked on What Rust: The Practical Guide says about packages, crates and modules, run.

What crates.io calls a crate

crates.io publishes packages and calls them crates, and the docs.rs page for serde is one crate's documentation because that package has exactly one library. The loose usage is harmless until a package has a library and two binaries, as this page's does: then "the crate" is three things, cargo build makes all three, and cargo run asks which one you meant. Publishing a crate is where the word is used in the loose sense on purpose.

Where this sits

Modules and visibility is the tree inside one crate. One module per file is how that tree maps onto files. Workspaces are the level above the package, several of them sharing one Cargo.lock and one target/ — which is what this page's demo/ folder is.

Practice

Where does crate:: point? For each of five places in one package — src/lib.rs, src/main.rs, src/bin/tool.rs, a #[cfg(test)] mod tests inside lib.rs, and tests/it.rs — write down what crate:: refers to, whether use my_package::X is available, and whether private items of lib.rs can be seen. Then say what the crate is called in each case.

Check the last question by printing module_path!() from the crate root and from two nested modules of one file compiled with bare rustc, and explain why src/main.rs is the one file whose crate name does not come from its own file name.

Solution

pkg_crate_kata.rs in full — pasted here by tools/run_examples.py from the file CI compiles and runs.

//! Kata solution: where `crate::` points from five places in one package, and
//! what this crate is called. The measured half comes from `module_path!()`.
//!
//!   rustc --edition 2024 pkg_crate_kata.rs -o /tmp/pck && /tmp/pck

mod product {
    pub mod category {
        pub fn path_here() -> &'static str {
            module_path!()
        }
    }

    pub fn path_here() -> &'static str {
        module_path!()
    }
}

fn main() {
    println!("1. What this crate is called, measured");
    println!("   module_path!() at the root        = {}", module_path!());
    println!("   module_path!() in product         = {}", product::path_here());
    println!("   module_path!() in product::category = {}", product::category::path_here());
    println!("   Under bare rustc the crate is named after the file: pkg_crate_kata.rs");
    println!("   gives pkg_crate_kata. Under Cargo a bin target is named after its file");
    println!("   too (src/bin/report.rs -> report), except src/main.rs, which takes the");
    println!("   package's name, and src/lib.rs, which takes it with `-` turned into `_`.");

    println!();
    println!("2. Where `crate::` points, from five places in one package");
    println!("   src/lib.rs               crate:: is lib.rs; `use my_package::X` is not");
    println!("                            available (a crate has no name for itself)");
    println!("   src/main.rs              crate:: is main.rs itself, a second crate;");
    println!("                            the library is `use my_package::X`");
    println!("   src/bin/tool.rs          crate:: is tool.rs, a third crate; it sees the");
    println!("                            library by name and main.rs not at all");
    println!("   #[cfg(test)] mod tests   inside lib.rs: crate:: is lib.rs, super:: is the");
    println!("                            enclosing module, private items included");
    println!("   tests/it.rs              its own crate, like a binary: `use my_package::X`,");
    println!("                            pub items only");

    println!();
    println!("3. The trap, from the page's demo");
    println!("   shared code in src/main.rs and `crate::shared::greeting()` in src/bin/other.rs");
    println!("   -> E0433: could not find `shared` in the crate root");
    println!("   the fix is a src/lib.rs: every binary of the package can `use` it by name");
}

Verified output of pkg_crate_kata.rs — regenerated by tools/run_examples.py, never hand-typed.

1. What this crate is called, measured
   module_path!() at the root        = pkg_crate_kata
   module_path!() in product         = pkg_crate_kata::product
   module_path!() in product::category = pkg_crate_kata::product::category
   Under bare rustc the crate is named after the file: pkg_crate_kata.rs
   gives pkg_crate_kata. Under Cargo a bin target is named after its file
   too (src/bin/report.rs -> report), except src/main.rs, which takes the
   package's name, and src/lib.rs, which takes it with `-` turned into `_`.

2. Where `crate::` points, from five places in one package
   src/lib.rs               crate:: is lib.rs; `use my_package::X` is not
                            available (a crate has no name for itself)
   src/main.rs              crate:: is main.rs itself, a second crate;
                            the library is `use my_package::X`
   src/bin/tool.rs          crate:: is tool.rs, a third crate; it sees the
                            library by name and main.rs not at all
   #[cfg(test)] mod tests   inside lib.rs: crate:: is lib.rs, super:: is the
                            enclosing module, private items included
   tests/it.rs              its own crate, like a binary: `use my_package::X`,
                            pub items only

3. The trap, from the page's demo
   shared code in src/main.rs and `crate::shared::greeting()` in src/bin/other.rs
   -> E0433: could not find `shared` in the crate root
   the fix is a src/lib.rs: every binary of the package can `use` it by name

If you are coming from another language

  • Python. A distribution (what pip installs, named in pyproject.toml ↗) contains import packages and modules; Rust's package → crate → module is the same three levels, with the crate as a compilation unit Python does not have. The underscore rule is Python's too: the distribution scikit-learn is imported as sklearn, and typing-extensions as typing_extensions, because an import name has to be an identifier.
  • Go. A Go module (go.mod) holds packages, and a package is one directory; Rust's module tree is inside the crate rather than one per directory, and a Go main package is the nearest thing to a binary crate.
  • Java. A Maven artifact is the package, a JAR is roughly the crate, and Java packages are the module tree. A JAR can hold several main classes and you pick one at run time; a Rust package builds one executable per binary crate and you pick at build time.
  • C and C++. No standard unit above the translation unit: a static library, a shared library or an executable is what a crate compiles into, and what a Makefile ↗ lists by hand is what cargo metadata discovers from the file layout.

See also

Po polsku

Pakiet (package) to katalog z Cargo.toml; skrzynia (crate) to jedna jednostka kompilacji — biblioteka albo program wykonywalny. Pakiet ma najwyżej jedną skrzynię biblioteczną (src/lib.rs) i dowolnie wiele binarnych (src/main.rs i po jednym pliku w src/bin/); każda ma własne drzewo modułów, dlatego plik w src/bin/ nie widzi modułów z main.rs (E0433, „could not find shared in the crate root”), a wspólny kod trafia do lib.rs, którego każdy program w pakiecie używa po nazwie pakietu. Nazwa biblioteki zamienia myślnik na podkreślenie, bo nazwa skrzyni musi być identyfikatorem: pakiet pg-pkg-two-crates to w kodzie pg_pkg_two_crates. cargo metadata wypisuje cele (targets), które Cargo naprawdę znajdzie — i pokazuje, że katalog bin/ obok src/ nie jest żadnym z nich; na rustc 1.98.0 cargo new wpisuje do manifestu edition = "2024".

Szukaj po polsku: pakiet a skrzynia · moduły w Ruście · rust package vs crate vs module · cargo src/bin shared code lib.rs · cargo metadata targets · rust crate name underscore dash