Web Analytics

Packages & Crates

Advanced ~35 min read

A crate is the smallest amount of code that the Rust compiler considers at a time. A package is one or more crates that provide a set of functionality. Understanding packages and crates is essential for organizing your Rust code and managing dependencies with Cargo.

What is a Crate?

A crate is a compilation unit in Rust. There are two types of crates:

1. Binary Crate

A binary crate is a program you can compile to an executable that you can run. Every package has at most one binary crate, and it must be in src/main.rs.

// src/main.rs - Binary crate
fn main() {
    println!("Hello, world!");
}

2. Library Crate

A library crate doesn't have a main() function and doesn't compile to an executable. Instead, it defines functionality intended to be shared with multiple projects. Library crates are in src/lib.rs.

// src/lib.rs - Library crate
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}
Crate Types: A package can contain both a binary crate (in src/main.rs) and a library crate (in src/lib.rs). The binary crate can use the library crate as if it were an external crate.

What is a Package?

A package is one or more crates that provide a set of functionality. A package contains a Cargo.toml file that describes how to build those crates.

Rules for packages:

  • A package must contain at most one library crate
  • A package can contain as many binary crates as you want
  • A package must contain at least one crate (either library or binary)

Cargo.toml File

The Cargo.toml file defines your package:

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

[dependencies]
serde = "1.0"
serde_json = "1.0"
Edition: The edition field specifies which Rust edition your package uses. Current editions are "2015", "2018", and "2021". Use "2021" for new projects.
Output
Click Run to execute your code

Managing Dependencies

Dependencies are external crates your package needs. They're specified in Cargo.toml:

Adding Dependencies

Add dependencies to the [dependencies] section:

[dependencies]
serde = "1.0"
serde_json = "1.0"
tokio = { version = "1.0", features = ["full"] }

Then run cargo build to download and compile dependencies.

Version Specifications

You can specify versions in different ways:

Specification Meaning Example
"1.0" Compatible with 1.0.x serde = "1.0"
"^1.0" Compatible with 1.x.x (default) serde = "^1.0"
"~1.0" Compatible with 1.0.x only serde = "~1.0"
"=1.0.0" Exact version serde = "=1.0.0"
"*" Any version (not recommended) serde = "*"

Dependency Sources

Dependencies can come from different sources:

1. From crates.io (Default)

[dependencies]
serde = "1.0"  # From crates.io

2. From Git Repository

[dependencies]
my_crate = { git = "https://github.com/user/repo", branch = "main" }

3. From Local Path

[dependencies]
my_crate = { path = "../my_crate" }

4. From Workspace

[dependencies]
my_crate = { workspace = true }
Output
Click Run to execute your code

Features

Many crates support optional features that you can enable:

[dependencies]
tokio = { version = "1.0", features = ["full", "macros"] }
serde = { version = "1.0", features = ["derive"] }
Feature Flags: Features allow you to include only the parts of a crate you need, reducing compilation time and binary size. Check the crate's documentation for available features.

Development Dependencies

Dependencies needed only for development (tests, benchmarks) go in [dev-dependencies]:

[dev-dependencies]
mockito = "1.0"
criterion = "0.5"
Build Dependencies: Dependencies needed only at build time go in [build-dependencies]. These are used by build scripts in build.rs.

Using External Crates

Once you've added a dependency to Cargo.toml, you can use it in your code:

// In Cargo.toml:
// [dependencies]
// serde = "1.0"

// In your code:
use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize)]
struct User {
    name: String,
    age: u32,
}

Publishing to crates.io

To publish your crate to crates.io:

  1. Create an account: Sign up at crates.io and get an API token
  2. Add metadata: Ensure your Cargo.toml has required fields:
    [package]
    name = "my_crate"
    version = "0.1.0"
    edition = "2021"
    description = "A short description"
    license = "MIT OR Apache-2.0"
    repository = "https://github.com/user/repo"
  3. Publish: Run cargo publish
Publishing is Permanent: Once published, you cannot delete a version. You can only yank it (mark it as unavailable for new projects). Plan your version numbers carefully!

Workspaces

A workspace is a set of packages that share a Cargo.lock and output directory:

# Cargo.toml at workspace root
[workspace]
members = [
    "crate1",
    "crate2",
    "crate3",
]

[workspace.dependencies]
shared_dep = "1.0"
Workspace Benefits: Workspaces allow you to manage multiple related packages together, share dependencies, and build them all at once with cargo build --workspace.

When to Use Packages vs Crates

Use a package when:

  • Creating a new project: Use cargo new to create a package
  • Organizing code: Group related functionality into packages
  • Sharing code: Publish packages to crates.io
  • Managing dependencies: Packages manage their own dependencies

Use multiple crates when:

  • Binary + library: Have both executable and reusable code
  • Multiple binaries: Need several executables in one package
  • Workspace: Managing multiple related packages

Best Practices

  • Use semantic versioning: Follow MAJOR.MINOR.PATCH versioning
  • Specify exact versions carefully: Use ^ for compatibility
  • Keep dependencies up to date: Use cargo update regularly
  • Use features wisely: Only enable features you need
  • Document your crate: Add README.md and documentation
  • Test before publishing: Run cargo test and cargo build --release
  • Use workspaces for monorepos: Manage multiple packages together

Common Mistakes

1. Not specifying edition in Cargo.toml

// Wrong - Missing edition
[package]
name = "my_project"
version = "0.1.0"

// Correct - Specify edition
[package]
name = "my_project"
version = "0.1.0"
edition = "2021"

2. Using wildcard versions for dependencies

// Wrong - Too permissive
[dependencies]
serde = "*"  // Can break with any update

// Correct - Specify compatible version
[dependencies]
serde = "1.0"  // Compatible with 1.x.x

3. Forgetting to add dependencies to Cargo.toml

// Wrong - Using crate without adding to Cargo.toml
use serde::Serialize;  // Error: can't find crate

// Correct - Add to Cargo.toml first
// [dependencies]
// serde = "1.0"
use serde::Serialize;  // OK

4. Not running cargo build after adding dependencies

# Wrong - Dependencies not downloaded
# Added to Cargo.toml but didn't run cargo build

# Correct - Build to download dependencies
cargo build

Exercise: Packages & Crates Practice

Task: Practice creating and managing packages and dependencies.

Requirements:

  • Create a Cargo.toml file with proper metadata
  • Add dependencies from crates.io
  • Understand the difference between binary and library crates
  • Learn about different dependency sources
Output
Click Run to execute your code
Show Solution
# Cargo.toml
[package]
name = "calculator"
version = "0.1.0"
edition = "2021"

[dependencies]
serde = "1.0"
serde_json = "1.0"

# Binary crate: src/main.rs (has main function)
# Library crate: src/lib.rs (no main function)
# Package: Contains Cargo.toml and one or more crates

# Dependency sources:
# 1. From crates.io: serde = "1.0"
# 2. From Git: my_crate = { git = "https://github.com/user/repo" }
# 3. From local: my_crate = { path = "../my_crate" }

Summary

  • Crate is the smallest compilation unit (binary or library)
  • Package is one or more crates with a Cargo.toml
  • Binary crate has main() and produces executable
  • Library crate has no main() and provides functionality
  • Cargo.toml defines package metadata and dependencies
  • Dependencies can come from crates.io, Git, or local paths
  • Use ^ version specifier for compatibility
  • Features allow optional functionality in crates
  • Workspaces manage multiple related packages
  • Publish to crates.io to share your crates

What's Next?

Now that you understand packages and crates, you'll learn about iterators - one of Rust's most powerful features for processing collections. Iterators allow you to write efficient, functional-style code that's both readable and performant.