RustCrates & Packages

Crates & Packages in Rust

Before you can understand Cargo, modules, or how Rust projects are structured, you need to know what a crate and a package are. These two terms are precise and distinct in Rust — and they are the foundation of everything else.

What is a Crate?

A crate is the smallest unit of compilation in Rust. Every time you run cargo build, the compiler processes one crate at a time. A crate is either:

  • A binary crate — has a main function, produces an executable.
  • A library crate — has no main, produces a .rlib file that other crates can link against.

The file where the compiler starts reading is called the crate root.

Crate type

Crate root

Output

Binary crate

src/main.rs

Executable binary

Library crate

src/lib.rs

.rlib (linkable library)

What is a Package?

A package is one or more crates managed together by Cargo. A package:

  • Contains a Cargo.toml file that describes the package's metadata and dependencies.
  • Can contain at most one library crate (src/lib.rs).
  • Can contain multiple binary crates (src/main.rs plus anything under src/bin/).

When you run cargo new my_project, Cargo creates a package with a single binary crate rooted at src/main.rs.

Bash
$ cargo new my_project
     Created binary (application) `my_project` package

$ cargo new my_lib --lib
     Created library `my_lib` package
Binary Crates

A binary crate compiles to an executable that can be run directly. It must contain a main function as its entry point. The default binary crate root is src/main.rs:

RUST
// src/main.rs — the crate root for the default binary
fn main() {
    println!("Hello from a binary crate!");
}

Bash
$ cargo run
   Compiling my_project v0.1.0
    Finished dev [unoptimized + debuginfo] target(s) in 0.42s
     Running `target/debug/my_project`
Hello from a binary crate!
Library Crates

A library crate exposes functions, types, and traits for other crates to use. It has no main function. The crate root is src/lib.rs:

RUST
// src/lib.rs — the crate root for a library crate

pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

pub struct Point {
    pub x: f64,
    pub y: f64,
}

impl Point {
    pub fn new(x: f64, y: f64) -> Self {
        Point { x, y }
    }

    pub fn distance_from_origin(&self) -> f64 {
        (self.x * self.x + self.y * self.y).sqrt()
    }
}
Note
A package can have both src/lib.rs and src/main.rs. In that case, the binary crate can use the library crate internally by name. This is a common pattern for CLI tools that also expose a library API.
Multiple Binary Crates with src/bin/

A package can contain multiple binary crates by placing source files in the src/bin/ directory. Each file becomes a separate binary:

Bash
my_project/
├── Cargo.toml
└── src/
    ├── lib.rs          ← library crate
    ├── main.rs         ← default binary (my_project)
    └── bin/
        ├── server.rs   ← extra binary (my_project-server)
        └── client.rs   ← extra binary (my_project-client)

Bash
# Run the default binary
cargo run

# Run a specific binary from src/bin/
cargo run --bin server
cargo run --bin client
The [[bin]] Section in Cargo.toml

For more control over binary names and paths, declare them explicitly in Cargo.toml using the [[bin]] array of tables (double brackets mean an array):

TOML
[package]
name    = "my_project"
version = "0.1.0"
edition = "2021"

[[bin]]
name = "server"
path = "src/server_main.rs"

[[bin]]
name = "client"
path = "src/client_main.rs"

[lib]
name = "my_project_lib"
path = "src/lib.rs"
The Crate Root

The crate root is the file the Rust compiler starts from when building a crate. It is the top of the module tree for that crate. Everything the crate contains must be reachable from this file through a chain of mod declarations or inline code.

  • Binary crate root: src/main.rs (or the path in [[bin]])
  • Library crate root: src/lib.rs (or the path in [lib])
External Crates as Dependencies

Rust's package registry is crates.io. To use an external crate, add it to the [dependencies] section of your Cargo.toml, then run cargo build to download and compile it:

TOML
[dependencies]
rand    = "0.8"
serde   = { version = "1.0", features = ["derive"] }
tokio   = { version = "1",   features = ["full"] }

RUST
// src/main.rs
use rand::Rng;

fn main() {
    let mut rng = rand::thread_rng();
    let n: u32 = rng.gen_range(1..=100);
    println!("Random number: {}", n);
}
Note
In Rust 2018 and later, you do not need to write extern crate rand;. Adding the dependency in Cargo.toml is enough — the crate is available automatically via use.
The extern crate Syntax (Pre-2018)

In Rust editions before 2018, you had to explicitly declare external crates at the crate root with extern crate. You may still see this in older code:

RUST
// Old style (edition 2015) — rarely needed today
extern crate serde;
extern crate rand;

use rand::Rng;
Tip
If you are writing new code, always use edition = "2021"in your Cargo.toml. The extern crate declaration is only required in 2021 for the special macro_rules! crate import pattern, which is also increasingly rare.
Crate Naming Conventions
  • snake_case for crate names: my_library, json_parser, web_server

  • Cargo converts hyphens in package names to underscores for use in Rust code: my-lib in Cargo.toml becomes my_lib in use statements

  • Keep names short and descriptive — they appear in every use path

  • Avoid generic names like utils or helpers — be specific about what the crate does

Private vs Public APIs in a Library Crate

A library crate's public API is everything marked pub in src/lib.rs and its submodules. Items without pub are private to the crate and cannot be accessed by downstream users.

Designing a good public API means:

  • Expose types and functions that users genuinely need

  • Hide implementation details — they can change without breaking callers

  • Use pub use to re-export deeply nested types at a convenient path

  • Document everything public with /// doc comments

  • Consider stability: once published, removing public items is a breaking change

RUST
// src/lib.rs

// Public — part of the library's API
pub struct Config {
    pub host: String,
    pub port: u16,
}

// Public constructor — hides the struct's internal complexity
impl Config {
    pub fn new(host: &str, port: u16) -> Self {
        Config {
            host: host.to_string(),
            port,
        }
    }
}

// Private — implementation detail, invisible to callers
fn validate_port(port: u16) -> bool {
    port > 1024
}

// pub(crate) — shared across this crate's own modules, not exported
pub(crate) fn internal_setup() {
    println!("internal setup");
}
Crate vs Module — Key Differences

Concept

Crate

Module

Definition

A compilation unit; produces a binary or library

A namespace within a crate

Boundary

Defined by the crate root file

Defined by mod blocks or separate files

Privacy boundary

pub(crate) stops here

pub(super) refers to parent module

Cargo involvement

Listed in Cargo.toml

No Cargo involvement

Can be published

Yes — to crates.io

No — modules live inside a crate

Publishing Your Own Crate

Publishing a library crate to crates.io makes it available for anyone to use as a dependency. The basic flow is:

  1. Write and test your library.
  2. Add required metadata to Cargo.toml: description, license, repository.
  3. Log in: cargo login (requires a crates.io account and API token).
  4. Publish: cargo publish.

See the dedicated crates-io page for the full publishing workflow, semantic versioning, and how to yank a bad release.

TOML
[package]
name        = "my_awesome_lib"
version     = "0.1.0"
edition     = "2021"
description = "A short description of what the library does"
license     = "MIT OR Apache-2.0"
repository  = "https://github.com/you/my_awesome_lib"
readme      = "README.md"
keywords    = ["parsing", "json"]
categories  = ["parser-implementations"]
Success
You now understand the distinction between crates and packages in Rust. A crate is the smallest compilation unit — either binary (produces an executable) or library (produces a linkable artifact). A package is one or more crates managed by Cargo via Cargo.toml. External crates are added as dependencies and accessed via use. A library crate's public API is everything explicitly marked pub — everything else is an implementation detail hidden from the outside world.