Cargo Basics
Cargo Basics
Cargo is Rust's build system and package manager. It handles downloading dependencies, compiling packages, making distributable packages, and uploading them to crates.io.
Creating a New Project
Binary Project
cargo new my_project
cd my_project
This creates:
my_project/
├── Cargo.toml
└── src/
└── main.rs
Library Project
cargo new my_library --lib
This creates:
my_library/
├── Cargo.toml
└── src/
└── lib.rs
Understanding Cargo.toml
The manifest file that contains metadata about your project:
[package]
name = "my_project"
version = "0.1.0"
edition = "2021"
authors = ["Your Name <[email protected]>"]
description = "A brief description"
license = "MIT"
[dependencies]
serde = "1.0"
tokio = { version = "1", features = ["full"] }
[dev-dependencies]
assert_cmd = "2.0"
[build-dependencies]
cc = "1.0"
Basic Cargo Commands
Building and Running
# Build the project
cargo build
# Build in release mode (optimized)
cargo build --release
# Run the project
cargo run
# Run with arguments
cargo run -- arg1 arg2
# Run in release mode
cargo run --release
Checking and Testing
# Check if code compiles (faster than build)
cargo check
# Run tests
cargo test
# Run specific test
cargo test test_name
# Run tests with output
cargo test -- --nocapture
# Run benchmarks
cargo bench
Documentation
# Generate documentation
cargo doc
# Generate and open documentation
cargo doc --open
# Include dependencies documentation
cargo doc --no-deps
Managing Dependencies
Adding Dependencies
Add to Cargo.toml
:
[dependencies]
rand = "0.8"
serde = { version = "1.0", features = ["derive"] }
my_crate = { path = "../my_crate" }
my_git_crate = { git = "https://github.com/user/repo.git" }
Using Dependencies
use rand::Rng;
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize)]
struct Config {
name: String,
value: i32,
}
fn main() {
let mut rng = rand::thread_rng();
let random: i32 = rng.gen_range(1..=100);
println!("Random number: {}", random);
}
Updating Dependencies
# Update dependencies to latest compatible versions
cargo update
# Update specific dependency
cargo update -p rand
# Generate Cargo.lock
cargo generate-lockfile
Cargo Workspaces
For managing multiple related packages:
Workspace Cargo.toml
[workspace]
members = [
"package1",
"package2",
"shared",
]
[workspace.dependencies]
serde = "1.0"
tokio = "1"
Member Package Cargo.toml
[package]
name = "package1"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = { workspace = true }
shared = { path = "../shared" }
Features
Conditional compilation and optional dependencies:
[features]
default = ["json"]
json = ["serde", "serde_json"]
async = ["tokio"]
full = ["json", "async"]
[dependencies]
serde = { version = "1.0", optional = true }
serde_json = { version = "1.0", optional = true }
tokio = { version = "1", optional = true }
Using features in code:
#[cfg(feature = "json")]
pub fn parse_json(data: &str) -> Result<Value, Error> {
serde_json::from_str(data)
}
#[cfg(feature = "async")]
pub async fn fetch_data(url: &str) -> Result<String, Error> {
// Async implementation
}
Build Scripts
Custom build logic with build.rs
:
// build.rs
fn main() {
// Generate code
println!("cargo:rerun-if-changed=build.rs");
// Compile C code
cc::Build::new()
.file("src/helper.c")
.compile("helper");
// Set environment variables
println!("cargo:rustc-env=BUILD_TIME={}", chrono::Utc::now());
}
Publishing to crates.io
Preparing for Publication
[package]
name = "unique-crate-name"
version = "0.1.0"
authors = ["Your Name <[email protected]>"]
edition = "2021"
description = "A useful crate"
readme = "README.md"
homepage = "https://example.com"
repository = "https://github.com/you/repo"
license = "MIT OR Apache-2.0"
keywords = ["utility", "helper"]
categories = ["development-tools"]
Publishing Commands
# Check package
cargo package --list
# Create .crate file
cargo package
# Publish (requires crates.io account)
cargo publish
# Publish dry run
cargo publish --dry-run
Cargo Configuration
.cargo/config.toml
[build]
target-dir = "target"
incremental = true
[target.x86_64-pc-windows-msvc]
linker = "rust-lld.exe"
[alias]
b = "build"
r = "run"
t = "test"
br = "build --release"
[env]
RUST_LOG = "debug"
Practical Examples
Multi-Binary Project
[[bin]]
name = "server"
path = "src/bin/server.rs"
[[bin]]
name = "client"
path = "src/bin/client.rs"
Run specific binary:
cargo run --bin server
cargo run --bin client
Example Tests
// src/lib.rs
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add() {
assert_eq!(add(2, 2), 4);
}
}
// examples/demo.rs
fn main() {
println!("2 + 2 = {}", my_crate::add(2, 2));
}
Run example:
cargo run --example demo
Integration Tests
// tests/integration_test.rs
use my_crate;
#[test]
fn test_integration() {
assert_eq!(my_crate::add(5, 5), 10);
}
Common Cargo Extensions
# Install cargo extension
cargo install cargo-watch cargo-edit cargo-audit
# Watch for changes and rebuild
cargo watch -x build
# Add dependency from command line
cargo add serde
# Check for security vulnerabilities
cargo audit
Best Practices
- Keep Cargo.toml organized with clear sections
- Use workspace for multi-package projects
- Pin versions in libraries, use ranges in applications
- Include Cargo.lock in version control for binaries
- Use features for optional functionality
- Write examples in the examples/ directory
- Document with cargo doc comments
- Use cargo fmt and cargo clippy for code quality
- Set up CI to run cargo test and cargo clippy
- Use semantic versioning for releases