Rust - Cargo Basics
Overview
Estimated time: 40–50 minutes
Master Cargo, Rust's built-in package manager and build system. Learn to create projects, manage dependencies, understand Cargo.toml, and use essential cargo commands for development.
Learning Objectives
- Create and manage Rust projects with cargo.
- Understand Cargo.toml configuration and dependency management.
- Use essential cargo commands for building, running, and testing.
- Work with crates from crates.io and local dependencies.
Prerequisites
What is Cargo?
Cargo is Rust's built-in package manager and build system that handles:
- Project creation: Setting up new Rust projects
- Dependency management: Adding and updating external crates
- Building: Compiling your code and dependencies
- Testing: Running unit and integration tests
- Documentation: Generating and viewing docs
- Publishing: Sharing crates with the community
Creating Projects
Binary Project
cargo new my_app
cd my_app
Creates this structure:
my_app/
├── Cargo.toml
├── src/
│ └── main.rs
└── .gitignore
Library Project
cargo new my_library --lib
cd my_library
Creates this structure:
my_library/
├── Cargo.toml
├── src/
│ └── lib.rs
└── .gitignore
Project in Existing Directory
mkdir existing_project
cd existing_project
cargo init # For binary
cargo init --lib # For library
Understanding Cargo.toml
Basic Structure
[package]
name = "my_app"
version = "0.1.0"
edition = "2021"
authors = ["Your Name "]
description = "A sample Rust application"
license = "MIT"
repository = "https://github.com/username/my_app"
[dependencies]
# External crates go here
[dev-dependencies]
# Development-only dependencies (for tests, etc.)
[build-dependencies]
# Dependencies for build scripts
Package Metadata
- name: Package name (must be unique on crates.io)
- version: Semantic version (major.minor.patch)
- edition: Rust edition (2015, 2018, 2021)
- authors: List of package authors
- description: Brief package description
- license: License identifier (MIT, Apache-2.0, etc.)
Essential Cargo Commands
Building and Running
# Build the project (debug mode)
cargo build
# Build with optimizations (release mode)
cargo build --release
# Build and run
cargo run
# Run with release optimizations
cargo run --release
# Check if code compiles (faster than build)
cargo check
Testing and Quality
# Run all tests
cargo test
# Run tests with output
cargo test -- --nocapture
# Run specific test
cargo test test_name
# Format code
cargo fmt
# Lint code
cargo clippy
# Generate and open documentation
cargo doc --open
Cleaning and Information
# Clean build artifacts
cargo clean
# Show dependency tree
cargo tree
# Show outdated dependencies
cargo outdated # Requires cargo-outdated plugin
Managing Dependencies
Adding Dependencies
Method 1: Edit Cargo.toml manually
[dependencies]
serde = "1.0"
tokio = { version = "1.0", features = ["full"] }
rand = "0.8"
Method 2: Use cargo add command
cargo add serde
cargo add tokio --features full
cargo add [email protected]
Dependency Version Specifiers
[dependencies]
# Exact version
serde = "=1.0.136"
# Compatible updates (default)
serde = "1.0" # means "^1.0.0" (>=1.0.0, <2.0.0)
# Tilde requirements
serde = "~1.0.136" # >=1.0.136, <1.1.0
# Wildcard
serde = "1.*" # >=1.0.0, <2.0.0
# Range
serde = ">=1.0, <1.5"
Dependency Features
[dependencies]
# Enable specific features
tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }
# Disable default features
reqwest = { version = "0.11", default-features = false, features = ["json"] }
# Optional dependencies
serde_json = { version = "1.0", optional = true }
[features]
default = ["serde_json"]
json_support = ["serde_json"]
Working with Examples
Creating a Simple Project
cargo new calculator
cd calculator
Edit Cargo.toml
[package]
name = "calculator"
version = "0.1.0"
edition = "2021"
[dependencies]
Edit src/main.rs
fn main() {
let a = 10.0;
let b = 3.0;
println!("Calculator Results:");
println!("{} + {} = {}", a, b, add(a, b));
println!("{} - {} = {}", a, b, subtract(a, b));
println!("{} * {} = {}", a, b, multiply(a, b));
println!("{} / {} = {:.2}", a, b, divide(a, b));
}
fn add(a: f64, b: f64) -> f64 {
a + b
}
fn subtract(a: f64, b: f64) -> f64 {
a - b
}
fn multiply(a: f64, b: f64) -> f64 {
a * b
}
fn divide(a: f64, b: f64) -> f64 {
if b != 0.0 {
a / b
} else {
panic!("Cannot divide by zero!");
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add() {
assert_eq!(add(2.0, 3.0), 5.0);
}
#[test]
fn test_subtract() {
assert_eq!(subtract(5.0, 3.0), 2.0);
}
#[test]
fn test_multiply() {
assert_eq!(multiply(4.0, 3.0), 12.0);
}
#[test]
fn test_divide() {
assert_eq!(divide(6.0, 2.0), 3.0);
}
}
Running the Project
cargo run
Expected Output:
Calculator Results:
10 + 3 = 13
10 - 3 = 7
10 * 3 = 30
10 / 3 = 3.33
Running Tests
cargo test
Expected Output:
Compiling calculator v0.1.0 (/path/to/calculator)
Finished test [unoptimized + debuginfo] target(s) in 0.31s
Running unittests src/main.rs (target/debug/deps/calculator-abc123)
running 4 tests
test tests::test_add ... ok
test tests::test_subtract ... ok
test tests::test_multiply ... ok
test tests::test_divide ... ok
test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Using External Crates
Adding a Random Number Generator
cargo add rand
Updated Cargo.toml
[package]
name = "calculator"
version = "0.1.0"
edition = "2021"
[dependencies]
rand = "0.8.5"
Using the rand crate
use rand::Rng;
fn main() {
let mut rng = rand::thread_rng();
let a: f64 = rng.gen_range(1.0..100.0);
let b: f64 = rng.gen_range(1.0..100.0);
println!("Random Calculator with {:.2} and {:.2}:", a, b);
println!("Sum: {:.2}", a + b);
println!("Product: {:.2}", a * b);
}
Expected Output (will vary due to randomness):
Random Calculator with 45.67 and 23.89:
Sum: 69.56
Product: 1090.75
Workspace Management
Creating a Workspace
For larger projects with multiple crates:
mkdir my_workspace
cd my_workspace
Create Cargo.toml for workspace
[workspace]
members = [
"app",
"utils",
"core"
]
resolver = "2"
Create member crates
cargo new app
cargo new utils --lib
cargo new core --lib
Structure
my_workspace/
├── Cargo.toml # Workspace manifest
├── app/
│ ├── Cargo.toml
│ └── src/main.rs
├── utils/
│ ├── Cargo.toml
│ └── src/lib.rs
└── core/
├── Cargo.toml
└── src/lib.rs
Using Local Dependencies
In app/Cargo.toml
:
[dependencies]
utils = { path = "../utils" }
core = { path = "../core" }
Build Profiles
Customizing Build Settings
[profile.dev]
opt-level = 0 # No optimization
debug = true # Include debug info
panic = "unwind" # Unwind on panic
[profile.release]
opt-level = 3 # Maximum optimization
debug = false # No debug info
panic = "abort" # Abort on panic
lto = true # Link-time optimization
Custom Profiles
[profile.dev-fast]
inherits = "dev"
opt-level = 1 # Light optimization for faster dev builds
[profile.release-debug]
inherits = "release"
debug = true # Release with debug info
Environment Variables and Config
Cargo Environment Variables
# Number of parallel jobs
export CARGO_BUILD_JOBS=4
# Target directory
export CARGO_TARGET_DIR=/tmp/my-target
# Offline mode
export CARGO_NET_OFFLINE=true
Per-project Config
Create .cargo/config.toml
:
[build]
target-dir = "target"
rustflags = ["-C", "link-arg=-fuse-ld=lld"]
[target.x86_64-unknown-linux-gnu]
linker = "clang"
[env]
RUST_LOG = "debug"
Publishing to crates.io
Preparing for Publication
[package]
name = "my-awesome-crate"
version = "0.1.0"
edition = "2021"
authors = ["Your Name "]
description = "A really awesome crate"
readme = "README.md"
homepage = "https://github.com/username/my-awesome-crate"
repository = "https://github.com/username/my-awesome-crate"
license = "MIT OR Apache-2.0"
keywords = ["awesome", "utility"]
categories = ["development-tools"]
Publishing Commands
# Login to crates.io
cargo login
# Publish (dry run first)
cargo publish --dry-run
cargo publish
Common Cargo Patterns
Development Dependencies
[dev-dependencies]
criterion = "0.4" # For benchmarking
proptest = "1.0" # Property-based testing
Platform-specific Dependencies
[target.'cfg(windows)'.dependencies]
winapi = "0.3"
[target.'cfg(unix)'.dependencies]
libc = "0.2"
Build Scripts
Create build.rs
in project root:
fn main() {
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rustc-env=BUILD_TIME={}", std::env::var("OUT_DIR").unwrap());
}
Then add to Cargo.toml
:
[build-dependencies]
chrono = "0.4"
Troubleshooting Common Issues
Dependency Resolution Conflicts
# Check dependency tree
cargo tree
# Update dependencies
cargo update
# Clear cache and rebuild
cargo clean
cargo build
Cargo.lock Issues
- Library projects: Add
Cargo.lock
to.gitignore
- Binary projects: Commit
Cargo.lock
to version control
Network Issues
# Use offline mode
cargo build --offline
# Use alternative registry
cargo build --registry my-registry
Best Practices
Project Organization
- Use workspaces for multi-crate projects
- Keep
Cargo.toml
organized with comments - Use semantic versioning properly
- Document dependencies and their purpose
Dependency Management
- Regularly update dependencies with
cargo update
- Review dependency licenses
- Use
cargo audit
to check for security vulnerabilities - Minimize dependencies to reduce build times
Development Workflow
- Use
cargo check
for fast iteration - Run
cargo clippy
regularly - Format code with
cargo fmt
before committing - Write and run tests with
cargo test
Common Pitfalls
- Not understanding semantic versioning: Can lead to breaking changes
- Overusing external dependencies: Increases compile time and complexity
- Ignoring Cargo.lock: Can cause version inconsistencies
- Not using workspaces: Makes large projects harder to manage
Checks for Understanding
- What's the difference between
cargo build
andcargo check
? - How do you add a dependency with specific features?
- What's the purpose of
Cargo.lock
? - How do you create a library project instead of a binary project?
- What does
cargo clean
do?
Answers
cargo build
compiles and generates executable,cargo check
only validates code without generating binariescargo add crate_name --features feature1,feature2
or edit Cargo.toml manually- Locks exact dependency versions to ensure reproducible builds
cargo new project_name --lib
- Removes all build artifacts and cached dependencies, forcing a clean rebuild