Rust - Build Tools

Overview

Estimated time: 40–50 minutes

Master the essential Rust build tools including cargo for project management, rustfmt for code formatting, clippy for linting, rustdoc for documentation, and various cargo plugins that enhance your development workflow.

Learning Objectives

Prerequisites

Cargo - Advanced Features

Project Structure and Workspaces

# Cargo.toml for a workspace
[workspace]
members = [
    "core",
    "cli", 
    "web",
    "shared"
]
resolver = "2"

[workspace.dependencies]
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1.0", features = ["full"] }

# In member crates, inherit workspace dependencies:
# [dependencies]
# serde = { workspace = true }

Cargo Commands Deep Dive

# Build commands
cargo build                    # Debug build
cargo build --release         # Optimized build
cargo build --target x86_64-pc-windows-gnu  # Cross-compile

# Check without building
cargo check                    # Fast syntax check
cargo check --all-targets     # Check tests, benchmarks, examples

# Testing commands
cargo test                     # Run all tests
cargo test --package mylib     # Test specific package
cargo test --doc              # Run doc tests only
cargo test -- --nocapture     # Show println! output

# Documentation
cargo doc                      # Generate docs
cargo doc --open              # Generate and open docs
cargo doc --no-deps           # Don't include dependency docs

# Publishing
cargo package                  # Create package for upload
cargo publish                  # Publish to crates.io
cargo yank --vers 1.0.0       # Yank a published version

# Information commands
cargo tree                     # Show dependency tree
cargo audit                    # Security audit (requires cargo-audit)
cargo outdated                 # Check for outdated deps (requires cargo-outdated)

Cargo Configuration

# .cargo/config.toml
[build]
target = "x86_64-unknown-linux-gnu"
rustflags = ["-C", "target-cpu=native"]

[target.x86_64-pc-windows-gnu]
linker = "x86_64-w64-mingw32-gcc"

[alias]
b = "build"
c = "check"
r = "run"
t = "test"
xtask = "run --package xtask --"

[registry]
default = "crates-io"
token = "your-token-here"  # Don't commit this!

[net]
git-fetch-with-cli = true

Rustfmt - Code Formatting

Basic Usage

# Format current crate
cargo fmt

# Check if formatting is needed (useful in CI)
cargo fmt -- --check

# Format specific files
rustfmt src/main.rs src/lib.rs

# Show what would be changed without modifying files
cargo fmt -- --check --verbose

Configuration

# rustfmt.toml or .rustfmt.toml
max_width = 100
hard_tabs = false
tab_spaces = 4
newline_style = "Unix"
use_small_heuristics = "Default"
reorder_imports = true
reorder_modules = true
remove_nested_parens = true
edition = "2021"

# Function call formatting
fn_args_layout = "Tall"
brace_style = "SameLineWhere"

# Import formatting
imports_layout = "Mixed"
group_imports = "StdExternalCrate"

# Comments and strings
format_code_in_doc_comments = true
normalize_comments = true
wrap_comments = true
comment_width = 80

Example Before and After

// Before formatting
use std::collections::HashMap;use std::fs::File;
use std::io::Read;

fn process_data(  data:   Vec<String>,
config:&HashMap<String,String>) -> Result<Vec<String>,
Box<dyn std::error::Error>>{
let mut results=Vec::new();
for item in data{
if let Some(processed)=config.get(&item){
results.push(processed.clone());}
}
Ok(results)
}

// After formatting  
use std::collections::HashMap;
use std::fs::File;
use std::io::Read;

fn process_data(
    data: Vec<String>,
    config: &HashMap<String, String>,
) -> Result<Vec<String>, Box<dyn std::error::Error>> {
    let mut results = Vec::new();
    for item in data {
        if let Some(processed) = config.get(&item) {
            results.push(processed.clone());
        }
    }
    Ok(results)
}

Clippy - Rust Linter

Basic Usage

# Run clippy on current crate
cargo clippy

# Run clippy on all targets (tests, benchmarks, examples)
cargo clippy --all-targets

# Treat warnings as errors
cargo clippy -- -D warnings

# Show additional information
cargo clippy -- -W clippy::pedantic

# Fix automatically fixable issues
cargo clippy --fix

Configuring Clippy

# clippy.toml
# Cognitive complexity threshold
cognitive-complexity-threshold = 30

# Maximum number of lines for a function
too-many-lines-threshold = 100

# Maximum number of arguments
too-many-arguments-threshold = 7

# Cyclomatic complexity threshold
cyclomatic-complexity-threshold = 25

Common Clippy Lints

// Examples of code that clippy will catch

// Unnecessary cloning
fn bad_clone(s: &String) -> String {
    s.clone()  // clippy::unnecessary_clone
}

fn good_clone(s: &str) -> String {
    s.to_string()
}

// Redundant pattern matching
fn bad_match(opt: Option<i32>) -> bool {
    match opt {
        Some(_) => true,
        None => false,  // clippy::redundant_pattern_matching
    }
}

fn good_match(opt: Option<i32>) -> bool {
    opt.is_some()
}

// Manual implementation of iterator methods
fn bad_loop(items: Vec<i32>) -> Vec<i32> {
    let mut result = Vec::new();  // clippy::manual_filter_map
    for item in items {
        if item > 0 {
            result.push(item * 2);
        }
    }
    result
}

fn good_functional(items: Vec<i32>) -> Vec<i32> {
    items.into_iter()
        .filter(|&x| x > 0)
        .map(|x| x * 2)
        .collect()
}

// Useless conversion
fn bad_conversion(s: String) -> String {
    s.into()  // clippy::useless_conversion
}

fn good_conversion(s: String) -> String {
    s
}

Configuring Lints in Code

// Allow specific lint for the entire crate
#![allow(clippy::too_many_arguments)]

// Deny specific lint (treat as error)
#![deny(clippy::unwrap_used)]

// Warn on specific lint
#![warn(clippy::missing_docs_in_private_items)]

// For specific functions
#[allow(clippy::too_many_lines)]
fn very_long_function() {
    // ... lots of code
}

// For modules
#[allow(clippy::module_name_repetitions)]
mod error_handling {
    pub struct ErrorHandlingConfig;
}

Rustdoc - Documentation Generation

Documentation Comments

/// This is a documentation comment for the following item.
/// It supports **markdown** formatting.
/// 
/// # Examples
/// 
/// ```
/// let result = add(2, 3);
/// assert_eq!(result, 5);
/// ```
/// 
/// # Panics
/// 
/// This function panics if the sum would overflow.
/// 
/// # Errors
/// 
/// Returns an error if the operation is not supported.
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

/// Module-level documentation
/// 
/// This module contains utility functions for mathematical operations.
pub mod math {
    //! Inner documentation for the module
    //! 
    //! This appears at the top of the module documentation.
    
    /// Calculate the factorial of a number
    pub fn factorial(n: u32) -> u64 {
        match n {
            0 | 1 => 1,
            _ => n as u64 * factorial(n - 1),
        }
    }
}

/// A configuration struct with documented fields
pub struct Config {
    /// The host address to bind to
    pub host: String,
    /// The port number to listen on
    pub port: u16,
    /// Maximum number of concurrent connections
    pub max_connections: usize,
}

impl Config {
    /// Create a new configuration with default values
    /// 
    /// # Examples
    /// 
    /// ```
    /// # use your_crate::Config;
    /// let config = Config::new();
    /// assert_eq!(config.port, 8080);
    /// ```
    pub fn new() -> Self {
        Config {
            host: "localhost".to_string(),
            port: 8080,
            max_connections: 100,
        }
    }
}

Rustdoc Commands

# Generate documentation
cargo doc

# Generate and open in browser
cargo doc --open

# Include private items
cargo doc --document-private-items

# Generate docs for dependencies too
cargo doc --include-deps

# Test documentation examples
cargo test --doc

# Generate docs with custom theme
rustdoc src/lib.rs --theme custom.css

Rustdoc Configuration

# In Cargo.toml
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]

[dependencies]
serde = { version = "1.0", optional = true }

[features]
default = []
serialization = ["serde"]

Useful Cargo Plugins

Installation and Usage

# Essential plugins
cargo install cargo-audit       # Security auditing
cargo install cargo-outdated    # Check outdated dependencies  
cargo install cargo-tree        # Dependency tree visualization
cargo install cargo-expand      # Expand macros
cargo install cargo-watch       # Watch for changes and rebuild
cargo install cargo-edit        # Add/remove dependencies from CLI
cargo install cargo-deny        # More advanced auditing
cargo install cargo-machete     # Find unused dependencies

# Usage examples
cargo audit                      # Check for security vulnerabilities
cargo outdated                   # Show outdated dependencies
cargo tree                       # Show dependency tree
cargo tree --duplicates         # Show duplicate dependencies
cargo expand                     # Expand macros in main.rs
cargo expand --lib               # Expand macros in lib.rs
cargo watch -x check             # Watch and run cargo check
cargo watch -x test              # Watch and run tests
cargo add serde --features derive # Add dependency with features
cargo rm unused-dep              # Remove dependency
cargo deny check                 # Advanced license/security checks
cargo machete                    # Find unused dependencies

Cargo Watch Examples

# Watch and run different commands
cargo watch -x "check --all-targets"
cargo watch -x "test -- --nocapture"
cargo watch -x fmt -x clippy
cargo watch -s "cargo check && cargo test"

# With custom ignore patterns
cargo watch -i "*.tmp" -i "target/*" -x check

# Clear screen between runs
cargo watch -c -x check

CI/CD Configuration

GitHub Actions Example

# .github/workflows/ci.yml
name: CI

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

env:
  CARGO_TERM_COLOR: always

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        rust: [stable, beta, nightly]
    
    steps:
    - uses: actions/checkout@v3
    
    - name: Install Rust
      uses: actions-rs/toolchain@v1
      with:
        toolchain: ${{ matrix.rust }}
        components: rustfmt, clippy
        override: true
    
    - name: Cache dependencies
      uses: actions/cache@v3
      with:
        path: |
          ~/.cargo/registry
          ~/.cargo/git
          target
        key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
    
    - name: Check formatting
      run: cargo fmt -- --check
    
    - name: Run clippy
      run: cargo clippy --all-targets -- -D warnings
    
    - name: Run tests
      run: cargo test --verbose
    
    - name: Run doc tests
      run: cargo test --doc
    
    - name: Generate docs
      run: cargo doc --no-deps
    
    - name: Security audit
      uses: actions-rs/audit-check@v1
      with:
        token: ${{ secrets.GITHUB_TOKEN }}

Makefile for Development

# Makefile
.PHONY: check test fmt clippy doc clean install

# Default target
all: check test

# Quick checks
check:
	cargo check --all-targets

# Run tests
test:
	cargo test
	cargo test --doc

# Format code
fmt:
	cargo fmt

# Check formatting
fmt-check:
	cargo fmt -- --check

# Run clippy
clippy:
	cargo clippy --all-targets -- -D warnings

# Generate documentation
doc:
	cargo doc --no-deps --open

# Clean build artifacts
clean:
	cargo clean

# Install locally
install:
	cargo install --path .

# Development workflow
dev: fmt clippy test

# CI workflow
ci: fmt-check clippy test doc

# Watch for changes
watch:
	cargo watch -c -s "make dev"

# Security audit
audit:
	cargo audit

# Check for outdated dependencies
outdated:
	cargo outdated

Advanced Build Scripts

build.rs Example

// build.rs
use std::env;
use std::fs;
use std::path::Path;

fn main() {
    // Tell cargo to re-run if files change
    println!("cargo:rerun-if-changed=build.rs");
    println!("cargo:rerun-if-changed=proto/");
    
    // Set environment variables
    println!("cargo:rustc-env=BUILD_TIME={}", chrono::Utc::now().to_rfc3339());
    
    // Conditional compilation based on features
    if cfg!(feature = "vendored-ssl") {
        println!("cargo:rustc-cfg=use_vendored_ssl");
    }
    
    // Link libraries
    if cfg!(target_os = "linux") {
        println!("cargo:rustc-link-lib=ssl");
        println!("cargo:rustc-link-lib=crypto");
    }
    
    // Generate code (example: protobuf)
    let out_dir = env::var("OUT_DIR").unwrap();
    let dest_path = Path::new(&out_dir).join("generated.rs");
    
    fs::write(
        &dest_path,
        r#"
        pub const BUILD_INFO: &str = "Built with Rust";
        "#,
    ).unwrap();
    
    // Include generated code in main crate:
    // include!(concat!(env!("OUT_DIR"), "/generated.rs"));
}

Performance and Optimization

Profile-Guided Optimization

# Step 1: Build with instrumentation
RUSTFLAGS="-Cprofile-generate=/tmp/pgo-data" cargo build --release

# Step 2: Run typical workloads
./target/release/myapp --typical-usage

# Step 3: Build with profile data
RUSTFLAGS="-Cprofile-use=/tmp/pgo-data" cargo build --release

Custom Profiles

# Cargo.toml
[profile.release]
lto = true              # Link-time optimization
codegen-units = 1       # Reduce parallelism for better optimization
panic = "abort"         # Smaller binary size

[profile.dev.package."*"]
opt-level = 2           # Optimize dependencies in debug builds

[profile.bench]
debug = true           # Include debug info in benchmarks

Summary

Rust build tools provide a comprehensive development environment:

Master these tools to maintain high code quality, consistency, and developer productivity in your Rust projects.


← Previous