Cargo Tutorial
Overview
Cargo is Rust's package manager and build system, handling dependency management, project building, test execution, documentation generation, and other tasks. This chapter will dive deep into Cargo's various features and best practices.
🚀 Cargo Basics
What is Cargo
Cargo is the core tool of the Rust ecosystem, providing:
- Project creation and management
- Dependency management and version control
- Code compilation and building
- Test execution and benchmarking
- Documentation generation and publishing
Cargo Project Structure
rust
// Typical Cargo project structure
my_project/
├── Cargo.toml // Project configuration file
├── Cargo.lock // Dependency lock file
├── src/ // Source code directory
│ ├── main.rs // Binary project entry point
│ ├── lib.rs // Library project entry point
│ └── bin/ // Additional binary files
├── examples/ // Example code
├── tests/ // Integration tests
├── benches/ // Performance benchmarks
├── build.rs // Build script
└── target/ // Build output directory📦 Project Creation and Management
Creating New Projects
bash
# Create binary project
cargo new my_binary_project
cd my_binary_project
# Create library project
cargo new my_library --lib
# Initialize project in existing directory
mkdir existing_project
cd existing_project
cargo init
# Create project with version control system
cargo new my_project --vcs git
cargo new my_project --vcs noneCargo.toml Configuration Details
toml
# Project basic information
[package]
name = "my_awesome_project" # Project name
version = "0.1.0" # Version number
edition = "2021" # Rust edition
authors = ["Your Name <your.email@example.com>"]
license = "MIT OR Apache-2.0" # Open source license
description = "An awesome Rust project" # Project description
homepage = "https://example.com" # Project homepage
repository = "https://github.com/username/project"
readme = "README.md" # README file
keywords = ["cli", "tool", "utility"] # Keywords
categories = ["command-line-utilities"] # Categories
# Dependencies configuration
[dependencies]
serde = "1.0" # Simple version
tokio = { version = "1.0", features = ["full"] } # With features
reqwest = { version = "0.11", default-features = false, features = ["json"] }
log = "0.4"
# Development dependencies (only used during development)
[dev-dependencies]
assert_cmd = "2.0"
tempfile = "3.0"
criterion = "0.5"
# Build dependencies
[build-dependencies]
cc = "1.0"
# Target-specific dependencies
[target.'cfg(windows)'.dependencies]
winapi = "0.3"
[target.'cfg(unix)'.dependencies]
nix = "0.26"
# Optional dependencies
[dependencies]
serde_json = { version = "1.0", optional = true }
[features]
default = ["json"] # Default features
json = ["serde_json"] # Custom features
# Binary targets
[[bin]]
name = "my_app"
path = "src/main.rs"
[[bin]]
name = "helper_tool"
path = "src/bin/helper.rs"
# Examples
[[example]]
name = "demo"
path = "examples/demo.rs"
# Benchmarks
[[bench]]
name = "my_benchmark"
harness = false
# Project metadata
[profile.release]
opt-level = 3 # Optimization level
debug = false # Debug info
strip = true # Strip symbols
lto = true # Link-time optimization
codegen-units = 1 # Code generation units
[profile.dev]
opt-level = 0
debug = true🔧 Dependency Management
Adding Dependencies
bash
# Add dependencies
cargo add serde
cargo add tokio --features full
cargo add reqwest --no-default-features --features json
# Add development dependencies
cargo add --dev assert_cmd
# Add build dependencies
cargo add --build cc
# Specify version
cargo add serde@1.0.150Version Specifications
toml
[dependencies]
# Exact version
serde = "=1.0.150"
# Compatible version (default)
serde = "1.0" # >=1.0.0, <2.0.0
serde = "1.0.150" # >=1.0.150, <2.0.0
# Semantic versioning
serde = "~1.0.150" # >=1.0.150, <1.1.0
serde = "^1.0.150" # >=1.0.150, <2.0.0
# Version range
serde = ">=1.0, <2.0"
# Git dependencies
tokio = { git = "https://github.com/tokio-rs/tokio.git" }
tokio = { git = "https://github.com/tokio-rs/tokio.git", branch = "main" }
tokio = { git = "https://github.com/tokio-rs/tokio.git", tag = "v1.0.0" }
tokio = { git = "https://github.com/tokio-rs/tokio.git", rev = "abc123" }
# Local path dependencies
my_lib = { path = "../my_library" }
# Conditional dependencies
[target.'cfg(windows)'.dependencies]
winapi = "0.3"Feature Management
rust
// lib.rs - Define features
#[cfg(feature = "json")]
pub mod json_support {
use serde_json;
pub fn parse_json(input: &str) -> serde_json::Result<serde_json::Value> {
serde_json::from_str(input)
}
}
#[cfg(feature = "xml")]
pub mod xml_support {
// XML processing functionality
}
// Use features
fn main() {
#[cfg(feature = "json")]
{
let data = r#"{"name": "Rust", "type": "Language"}"#;
match json_support::parse_json(data) {
Ok(value) => println!("Parse successful: {:?}", value),
Err(e) => println!("Parse failed: {}", e),
}
}
}bash
# Build with specific features enabled
cargo build --features json
cargo build --features "json,xml"
cargo build --no-default-features --features json
cargo build --all-features🏗️ Building and Compilation
Basic Build Commands
bash
# Check code (quick syntax check)
cargo check
# Build project
cargo build
# Release build (optimized)
cargo build --release
# Build specific target
cargo build --bin my_app
cargo build --example demo
cargo build --lib
# Cross-compilation
cargo build --target x86_64-pc-windows-gnu
cargo build --target wasm32-unknown-unknownBuild Configuration
toml
# Build configuration in Cargo.toml
[profile.dev]
opt-level = 0 # No optimization
debug = true # Include debug info
overflow-checks = true # Integer overflow checks
[profile.release]
opt-level = 3 # Highest optimization
debug = false # No debug info
lto = true # Link-time optimization
panic = "abort" # Abort on panic
[profile.test]
opt-level = 0
debug = true
# Custom profile
[profile.production]
inherits = "release"
opt-level = 3
debug = false
strip = true
lto = "fat"Conditional Compilation
rust
// Conditional compilation based on OS
#[cfg(target_os = "windows")]
fn get_config_dir() -> String {
"C:\\ProgramData\\MyApp".to_string()
}
#[cfg(target_os = "linux")]
fn get_config_dir() -> String {
"/etc/myapp".to_string()
}
#[cfg(target_os = "macos")]
fn get_config_dir() -> String {
"/Library/Application Support/MyApp".to_string()
}
// Conditional compilation based on features
#[cfg(feature = "async")]
async fn async_function() -> Result<String, Box<dyn std::error::Error>> {
let response = reqwest::get("https://api.example.com/data").await?;
let text = response.text().await?;
Ok(text)
}
#[cfg(not(feature = "async"))]
fn sync_function() -> Result<String, Box<dyn std::error::Error>> {
// Synchronous implementation
Ok("Synchronous data".to_string())
}
// Debug-only code
#[cfg(debug_assertions)]
fn debug_only_function() {
println!("This only runs in debug builds");
}
// Release-only code
#[cfg(not(debug_assertions))]
fn release_only_function() {
// Release version optimized code
}🧪 Testing and Benchmarks
Unit Tests
rust
// src/lib.rs
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
pub fn divide(a: f64, b: f64) -> Result<f64, String> {
if b == 0.0 {
Err("Cannot divide by zero".to_string())
} else {
Ok(a / b)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add() {
assert_eq!(add(2, 3), 5);
assert_eq!(add(-1, 1), 0);
}
#[test]
fn test_divide_success() {
assert_eq!(divide(10.0, 2.0).unwrap(), 5.0);
}
#[test]
fn test_divide_by_zero() {
assert!(divide(10.0, 0.0).is_err());
}
#[test]
#[should_panic]
fn test_panic() {
panic!("This test should panic");
}
#[test]
#[ignore]
fn expensive_test() {
// Expensive test, not run by default
}
}Integration Tests
rust
// tests/integration_test.rs
use my_project;
#[test]
fn test_public_api() {
let result = my_project::add(2, 3);
assert_eq!(result, 5);
}
#[test]
fn test_error_handling() {
let result = my_project::divide(10.0, 0.0);
assert!(result.is_err());
}Documentation Tests
rust
/// Calculate the sum of two numbers
///
/// # Examples
///
/// ```
/// use my_project::add;
///
/// let result = add(2, 3);
/// assert_eq!(result, 5);
/// ```
///
/// # Notes
///
/// This function may overflow on integer overflow
///
/// ```should_panic
/// use my_project::add;
///
/// // This will cause overflow (on some platforms)
/// let result = add(i32::MAX, 1);
/// ```
pub fn add(a: i32, b: i32) -> i32 {
a + b
}Performance Benchmarks
rust
// benches/benchmark.rs
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use my_project::*;
fn bench_add(c: &mut Criterion) {
c.bench_function("add", |b| {
b.iter(|| add(black_box(100), black_box(200)))
});
}
fn bench_divide(c: &mut Criterion) {
c.bench_function("divide", |b| {
b.iter(|| divide(black_box(100.0), black_box(3.0)))
});
}
criterion_group!(benches, bench_add, bench_divide);
criterion_main!(benches);Test Commands
bash
# Run all tests
cargo test
# Run specific tests
cargo test test_add
cargo test integration
# Show test output
cargo test -- --nocapture
# Run ignored tests
cargo test -- --ignored
# Parallel test control
cargo test -- --test-threads=1
# Run documentation tests
cargo test --doc
# Run benchmarks
cargo bench
# Code coverage (requires cargo-tarpaulin)
cargo install cargo-tarpaulin
cargo tarpaulin --out Html📚 Documentation Generation
Generating Documentation
bash
# Generate and open documentation
cargo doc --open
# Generate documentation with private items
cargo doc --document-private-items
# Generate dependency documentation
cargo doc --no-deps
# Only check documentation examples
cargo test --docDocumentation Comments
rust
//! # My Crate
//!
//! This is the top-level documentation for my crate.
//!
//! ## Features
//!
//! - Mathematical operations
//! - Error handling
//! - Async support
/// Represents a calculator
///
/// # Examples
///
/// ```
/// use my_project::Calculator;
///
/// let calc = Calculator::new();
/// let result = calc.add(2, 3);
/// assert_eq!(result, 5);
/// ```
pub struct Calculator {
/// Current value
pub value: i32,
}
impl Calculator {
/// Create a new calculator
///
/// # Examples
///
/// ```
/// # use my_project::Calculator;
/// let calc = Calculator::new();
/// assert_eq!(calc.value, 0);
/// ```
pub fn new() -> Self {
Self { value: 0 }
}
/// Perform addition
///
/// # Arguments
///
/// * `a` - First number
/// * `b` - Second number
///
/// # Returns
///
/// Returns the sum of two numbers
///
/// # Examples
///
/// ```
/// # use my_project::Calculator;
/// let calc = Calculator::new();
/// let result = calc.add(10, 20);
/// assert_eq!(result, 30);
/// ```
pub fn add(&self, a: i32, b: i32) -> i32 {
a + b
}
}📦 Publishing and Distribution
Preparing for Publishing
bash
# Check if package can be published
cargo publish --dry-run
# Package the project
cargo package
# View package contents
cargo package --listPublishing to crates.io
bash
# Login to crates.io (requires API token)
cargo login
# Publish package
cargo publish
# Yank version (within 72 hours)
cargo yank --vers 1.0.1
# Unyank
cargo yank --vers 1.0.1 --undoLocal Installation
bash
# Install from crates.io
cargo install my_tool
# Install from local path
cargo install --path .
# Install from Git repository
cargo install --git https://github.com/user/repo
# Uninstall
cargo uninstall my_tool🛠️ Advanced Features
Workspaces
toml
# Root Cargo.toml
[workspace]
members = [
"app",
"lib",
"tools/*",
]
[workspace.dependencies]
serde = "1.0"
tokio = "1.0"
# app/Cargo.toml
[package]
name = "app"
version = "0.1.0"
edition = "2021"
[dependencies]
lib = { path = "../lib" }
serde = { workspace = true }Build Scripts
rust
// build.rs
use std::env;
use std::path::PathBuf;
fn main() {
// Set environment variable
println!("cargo:rustc-env=BUILD_TIME={}", chrono::Utc::now());
// Link external library
println!("cargo:rustc-link-lib=ssl");
println!("cargo:rustc-link-search=native=/usr/local/lib");
// Rebuild conditions
println!("cargo:rerun-if-changed=src/proto/");
// Generate code
let out_dir = env::var("OUT_DIR").unwrap();
let dest_path = PathBuf::from(&out_dir).join("generated.rs");
std::fs::write(
&dest_path,
"pub const GENERATED: &str = \"Hello from build script!\";",
).unwrap();
}Custom Commands
bash
# Install useful Cargo extensions
cargo install cargo-edit # cargo add, cargo rm
cargo install cargo-watch # cargo watch
cargo install cargo-expand # macro expansion
cargo install cargo-audit # security audit
cargo install cargo-outdated # check outdated dependencies
cargo install cargo-tree # dependency tree
cargo install cargo-deny # dependency checking📝 Chapter Summary
By studying this chapter, you should have mastered:
Cargo Core Features
- ✅ Project creation and structure management
- ✅ Dependency management and version control
- ✅ Build configuration and optimization
- ✅ Testing and benchmarking
Advanced Features
- ✅ Feature flags and conditional compilation
- ✅ Workspace management
- ✅ Documentation generation and publishing
- ✅ Build scripts and custom commands
Best Practices
- Use semantic versioning
- Organize project structure properly
- Write comprehensive tests and documentation
- Use feature flags to manage functionality
Continue Learning: Next Chapter - Rust Quick Start