Introduction: Why CI/CD is Critically Important for Rust Projects
Rust is a systems programming language renowned for its performance and memory safety. However, even the most reliable code requires constant verification. Manual testing and building consume time and are prone to errors. CI/CD (Continuous Integration/Continuous Deployment) automates these processes, ensuring that every code change is checked by the compiler, tests, and linters before it reaches the main branch.
GitHub Actions is a built-in platform for automating workflows in GitHub. It is ideal for Rust projects due to its flexibility, free minute limit for public repositories, and a vast ecosystem of ready-made actions. In this article, we will cover the complete cycle of setting up CI/CD for Rust: from basic build to optimization using caching and deployment.
1. Basics: First Workflow for Building and Testing
Let's start by creating a minimal workflow. In the root of your repository, create a folder .github/workflows and a file ci.yml.
Basic ci.yml file
name: Rust CI
on: push: branches: [ "main" ] pull_request: branches: [ "main" ]
env: CARGO_TERM_COLOR: always
jobs: build: runs-on: ubuntu-latest
steps: - uses: actions/checkout@v4 - name: Install Rust uses: dtolnay/rust-toolchain@stable - name: Build run: cargo build --verbose - name: Run tests run: cargo test --verboseBreakdown of key elements:
- on: Triggers — runs on push to main and on Pull Request creation.
- env: The
CARGO_TERM_COLORvariable enables colored log output. - actions/checkout: Standard action for cloning the repository.
- dtolnay/rust-toolchain: The most popular action for installing Rust. Use
stable,nightly, or a specific version.
This workflow is already useful: it guarantees that the code compiles and all tests pass. However, this is not enough for a real project.
2. Optimization: Dependency Caching and Multi-Platform Build
Each CI run downloads all dependencies from scratch. For Rust projects with many crates, this can take minutes. The solution is caching the ~/.cargo and target folders.
Adding Caching
jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install Rust uses: dtolnay/rust-toolchain@stable - name: Cache dependencies uses: actions/cache@v3 with: path: | ~/.cargo/bin/ ~/.cargo/registry/index/ ~/.cargo/registry/cache/ target/ key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - name: Build run: cargo build --verbose - name: Run tests run: cargo test --verboseKey points:
- hashFiles('**/Cargo.lock'): The cache is invalidated only when
Cargo.lockchanges. - path: We cache binaries, the registry index, and the
targetfolder. - Restoration: If the key matches, the cache is restored, and cargo skips downloading.
Multi-Platform Build
Rust supports many platforms. To ensure your code works everywhere, use a build matrix:
jobs: build: strategy: matrix: os: [ubuntu-latest, windows-latest, macos-latest] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - name: Install Rust uses: dtolnay/rust-toolchain@stable