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

Prerequisites

What is Cargo?

Cargo is Rust's built-in package manager and build system that handles:

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

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

Network Issues

# Use offline mode
cargo build --offline

# Use alternative registry
cargo build --registry my-registry

Best Practices

Project Organization

Dependency Management

Development Workflow

Common Pitfalls

Checks for Understanding

  1. What's the difference between cargo build and cargo check?
  2. How do you add a dependency with specific features?
  3. What's the purpose of Cargo.lock?
  4. How do you create a library project instead of a binary project?
  5. What does cargo clean do?

Answers

  1. cargo build compiles and generates executable, cargo check only validates code without generating binaries
  2. cargo add crate_name --features feature1,feature2 or edit Cargo.toml manually
  3. Locks exact dependency versions to ensure reproducible builds
  4. cargo new project_name --lib
  5. Removes all build artifacts and cached dependencies, forcing a clean rebuild

← PreviousNext →