What Are Packages and Crates?

Understand Rust's fundamental compilation units and how Cargo packages organize them into libraries, binaries, and larger projects.

When you write a Rust program, the compiler doesn’t think in terms of individual .rs files the way many other languages do. Instead, it works with a larger concept called a crate — the smallest piece of code that the compiler ever compiles on its own. A package, on the other hand, is how Cargo (Rust’s build tool and package manager) groups one or more crates together, along with metadata and dependencies, into a single shippable unit.

These two terms form the foundation of Rust’s module system. If you misidentify which container does what, you’ll hit confusing errors and struggle to lay out a project correctly. This section makes the distinction concrete, shows you how a typical package is structured, and prepares you for everything that follows about modules and visibility.

What a Crate Is

A crate is the unit of compilation in Rust. When you run rustc some_file.rs, the compiler treats that file as a complete crate. It doesn’t care about other .rs files unless they’re brought in through module declarations (which you’ll see later). This is different from C or C++, where each source file compiles to an object file, or Java, where the compiler can process many files together. In Rust, all the source files that belong to one crate get woven into a single compilation target — an executable or a library.

Every crate starts from a single crate root file. That root file is the entry point that rustc reads first. From there, the compiler discovers the rest of the crate through mod statements. The crate root also defines the top-level module of the crate; we’ll explore modules in depth starting from the next chapter.

Crate root vs. file:

The crate root is not some special configuration — it’s simply the source file that you pass to the compiler (or that Cargo designates by convention). For a binary crate, that’s usually src/main.rs. For a library crate, it’s src/lib.rs.

Binary Crates and Library Crates

Every crate has one of two forms. Choosing the right one determines what you can do with the compiled output.

Binary Crates

A binary crate produces an executable — a program you can run from the terminal, like a CLI tool or a server. It must contain a function called main somewhere in its module tree that acts as the program’s entry point. The compiler expects exactly one main function per binary crate.

Most small programs you write will be binary crates. When you run cargo new my-app, Cargo scaffolds a package with a single binary crate.

Library Crates

A library crate produces a library — a .rlib file that other crates can link against and use. It has no main function because it’s not meant to be run directly. Instead, it exposes reusable functions, types, and traits. The rand crate you used in Chapter 2 is a library crate.

In everyday Rust conversation, “crate” usually means library crate, and the word is used interchangeably with “library.” A crate like serde or tokio is a library crate that you add as a dependency.

Correct output:

If you compile a library crate (with cargo build) and see a file like target/debug/lib<name>.rlib, the crate was built as a library. For a binary crate, you’ll see an executable in target/debug/<name>.

The Crate Root

The crate root is the file that marks the start of a crate’s module tree. It’s where rustc begins reading, and it’s the parent of all other modules in that crate. Even if the crate is split across dozens of files, they all originate from this single starting point.

Cargo uses well‑known file names to identify the crate root without you having to specify it. This is purely a convention, but it’s universal in the Rust ecosystem:

  • If a package contains src/main.rs, Cargo treats that file as the crate root of a binary crate whose name matches the package.
  • If a package contains src/lib.rs, Cargo treats that file as the crate root of a library crate, again named after the package.
  • You can have additional binary crates by placing files inside src/bin/; each .rs file in that directory becomes its own binary crate, with its own main function. The crate name for each of these is the filename without the .rs extension.

These three conventions allow Cargo to discover all crates in a package automatically — no configuration in Cargo.toml is needed for this basic structure.

Don’t mix conventions:

It’s possible to configure custom crate roots in Cargo.toml, but doing so adds complexity and can confuse tools that rely on the standard layout. Unless you have a specific, documented reason, stick to src/main.rs, src/lib.rs, and src/bin/.

Packages — The Cargo View

While a crate is what the compiler sees, a package is what Cargo manages. A package is a directory that contains a Cargo.toml file and at least one crate. The Cargo.toml describes the package’s metadata (name, version, authors) and lists its dependencies. It’s the manifest that tells Cargo which crates belong together and how to build them.

The Rust compiler itself has no concept of a package. You could compile a single .rs file with rustc directly, and that’s just a crate — no Cargo.toml needed. But in practice, almost all Rust projects use Cargo, and the package is the unit you check into version control, share on crates.io, or list as a dependency.

Rules for Package Contents

  • A package must contain at least one crate — either a library crate or a binary crate.
  • It can have at most one library crate.
  • It can have any number of binary crates.
  • Both library and binary crates can coexist in the same package.

If you’ve ever seen a project with both src/main.rs and src/lib.rs, you’ve seen a package that contains a library crate and a binary crate side by side. That layout is extremely common: the library holds the core logic, and the binary provides a thin main function that calls into it.

Only one library crate per package:

You cannot have two files that Cargo interprets as library crate roots (for instance, both src/lib.rs and src/my_lib.rs declared as lib targets in Cargo.toml). If you need multiple libraries, split them into separate packages or use a workspace.

How a Package Organizes Its Crates

Let’s make this concrete by examining what Cargo generates. When you run cargo new my-project, you get a package with a single binary crate.

1

Step 1: Create a new binary package

cargo new my-project

Cargo creates a directory named my-project with a Cargo.toml and a src/ folder containing main.rs.

2

Step 2: Inspect the directory structure

my-project
├── Cargo.toml
└── src
    └── main.rs

The Cargo.toml is the package manifest. There’s no explicit reference to src/main.rs inside it — Cargo infers the binary crate from the file’s presence.

3

Step 3: Examine the crate root

The file src/main.rs is the crate root of a binary crate named my-project. If you open it, you’ll find a fn main() that serves as the executable’s entry point.

4

Step 4: Build and confirm

Run cargo build inside the package. Cargo compiles the binary crate and places the executable in target/debug/my-project (or my-project.exe on Windows).

To create a library crate instead, you’d use cargo new my-lib --lib. That command generates src/lib.rs instead of main.rs, and Cargo treats the package as containing only a library crate.

If you later add both src/main.rs and src/lib.rs to the same package, Cargo will produce two crates from that one package — both named after the package — one library and one executable. The binary can then call into the library’s public functions, which keeps the main.rs file tiny and focused on wiring things together.

Putting a Crate on the Web

When a Rust developer says they “published a crate,” they almost always mean a package. The ecosystem around crates.io revolves around packages because a package is the thing that has a Cargo.toml with a name and version. When you add a dependency like rand = "0.8" to your Cargo.toml, you’re pulling in an entire package — which might contain a library crate, several binary crates, or both. Cargo then builds and links the library crate from that package into your project.

This is why distinguishing packages from crates matters: you depend on a package, but you use its library crate in your code.

Cargo itself is a package:

The cargo command‑line tool you use every day is itself a Rust package. It contains a binary crate (the tool you run in the terminal) and a library crate that other tools can depend on to interact with Cargo’s internals.

Common Mistakes and Misconceptions

Beginners often trip over a few predictable points. Recognizing these early saves hours of debugging.

Thinking a package equals a crate

A package is a Cargo concept; a crate is a compiler concept. A package can contain multiple crates. Referring to your whole project as “a crate” is harmless in casual conversation, but in technical discussions, it can lead to confusion when you actually have both a lib and a binary.

Expecting multiple library crates in one package

This is one of the most frequent causes of build errors. You cannot have two library crates in one package. If you need multiple independent libraries, each should live in its own package (or be organized as modules within a single library crate, which you’ll learn about next).

Naming mismatches with cargo new

When you create a package with cargo new my-app, the binary crate is automatically named my-app. If you later rename the directory but not the Cargo.toml, the crate name remains the same — because it’s derived from the package name in the manifest, not the folder name.

Misplacing the main function

A binary crate must have exactly one main function somewhere in its module tree. If you place code with a main inside src/lib.rs (the library crate root), the compiler will complain that a library crate can’t have a main. Similarly, if you have multiple files in src/bin/ and one of them lacks a main, that binary won’t compile.

src/main.rs is special only to Cargo:

If you compile src/main.rs directly with rustc, it’s just a file. But when Cargo sees it in the standard location, it knows to treat it as a binary crate root. Moving it elsewhere or renaming it without telling Cargo will break the build.

How This Fits into a Real Project

When you work on a non‑trivial application, you’ll often use a package that contains both a library crate and a binary crate. The library holds all the logic — parsing, database access, core algorithms — while the binary crate provides a thin main that reads arguments, sets up logging, and calls into the library. This separation gives you several advantages:

  • The library can be integration‑tested directly, something you can’t easily do with a binary crate.
  • Other developers can reuse your library in their own tools without running your binary.
  • If you later need a second binary (for example, an administration tool alongside the main server), you can add it under src/bin/ and let it share the same library.

As projects grow further, you might split them across multiple packages using Cargo workspaces. For now, the single‑package‑with‑library‑and‑binary pattern is the foundation you’ll use in most Rust projects.

Summary

A crate is the unit the compiler works with — either a binary that produces an executable or a library that produces reusable code. A package is what Cargo manages — a directory with a Cargo.toml that bundles one or more crates together under a single name, version, and set of dependencies. Every Rust project you encounter is built from these building blocks.