Rust pre-commit hooks

How to add git pre-commit hooks to rust projects.
rust
how-to
Published

2024-06-01

Modified

2024-06-02

This guide will show you how to:

  • Add rustfmt and clippy components to cargo.
  • Add cargo check, rustfmt, clippy, and test git pre-commit hooks to a rust project.
Pre-requisite Installations
  • A stable rust toolchain and cargo (e.g. using rustup).

Introduction

Having code checks, linting, formatting, and tests run automatically during CI runs is very advantageous. In particular, it helps enforce code standards and quality. From a rust perspective, having cargo on hand to do this is very helpful. However, executing these locally during development is a manual process, which can sometimes cause issues - as humans we forget a lot! In those cases, code could be pushed to a CI runner which then fails because there were problems when running the linter, formatter, and/or tests.

Pre-commits are renowned in many programming languages (perhaps most notably the pre-commit framework itself). Pre-commit hooks can help prevent these issues by executing checks automatically before committing.

This guide explores two options for adding pre-commit hooks within rust projects - see the Setting up pre-commit hooks section for more details. Both rely on cargo’s additional components rustfmt formatter and clippy linter, so the following section starts by showing how these can be added to cargo.

… they rely on users to install the pre-commit hooks locally, which presents another opportunity for us humans to make mistakes (by not installing before committing, for example). In this case pre-receive hooks maybe a better option. However, pre-receive hooks on GitHub seem to be an Enterprise only solution 😞.

Adding rustfmt and clippy

rustfmt and clippy can be added as additional components to cargo.

  1. Install rustfmt:
rustup component add rustfmt
  1. Install clippy:
rustup component add clippy

Setting up pre-commit hooks

This section provides two sets of instructions for adding pre-commit hooks within a rust project. Option 1 uses pre-commit, which is a useful approach for those looking for an ‘off-the-shelf’ solution. Option 2 explores writing hooks directly, meaning no additional external dependencies are required.

They add the following hooks:

Hook Description
cargo check Check source code and dependencies for compilation errors.
cargo fmt Check source code for formatting inconsistencies.
cargo clippy Run lints to check for common issues.
cargo test Run whole test suite.
Note

The cargo test hook is only available when using Option 2.

Option 1: Using pre-commit

The instructions in this section use pre-commit and the currently supported rust pre-commit hooks. See here for other supported hooks.

  1. Follow the latest pre-commit installation instructions.

  2. Within your git version controlled rust project root directory, create a .pre-commit-config.yaml file:

touch .pre-commit-config.yaml
  1. Open the file and add the following contents:
repos:
-   repo: https://github.com/doublify/pre-commit-rust
    rev: v1.0
    hooks:
    -   id: cargo-check
        args: ["--workspace"]
    -   id: fmt
        args: ["--", "--check"]
    -   id: clippy
        args: ["--", "-D", "warnings"]
Note

Check and update rev to the latest version before use. The above repo url will provide the latest version number.

  1. Install the pre-commits:
pre-commit install
  1. Check the pre-commit hooks are working by calling:
pre-commit run --all-files
  1. Enjoy using rust with pre-commits 🥳

Option 2: Writing own hooks

Note

The instruction in this section will work on MacOS and Linux, but not on Windows.

This approach works by adding a script that executes when attempting to commit. It runs the necessary cargo compilation, formatting, and linting checks, then blocks the commit if an issue is detected at any stage.

The following instructions add the same check, rustfmt, and clippy pre-commit checks as shown in the previous section. To demonstrate the flexibility of this approach, there is a version of the script where the test suite is also executed as part of the pre-commit checks (which is not currently achievable with the previous option). Adding test suite execution is purely a design choice, trading off between execution speed and risk of any changes causing test failures on the runners.

  1. Within your git version controlled rust project directory, create a pre-commit file in the .git/hooks/ directory:
touch .git/hooks/pre-commit
  1. Open the file, and add the following content:
#!/bin/sh

set -eu

# formatting variables
GREEN='\033[0;32m'
RED='\033[0;31m'
NC='\033[0m' # No Color
BOLD=$(tput bold)
NORM=$(tput sgr0)

echo "Running pre-commit checks..."

# cargo check hook
if ! cargo check --workspace
then
    echo -e "cargo check: ......... ${RED}nok${NC}"
    echo -e "${RED}Pre-commit: Issues detected when calling 'cargo check'."
    exit 1
fi

echo -e "cargo check: ......... ${GREEN}ok${NC}"

# cargo rustfmt hook
if ! cargo fmt -- --check
then
    echo -e "cargo rustfmt: ....... ${RED}nok${NC}"
    echo -e "${RED}Pre-commit: Code style issues detected with rustfmt."
    exit 1
fi

echo -e "cargo rustfmt: ....... ${GREEN}ok${NC}"

# cargo clippy hook
if ! cargo clippy --all-targets -- -D warnings
then
    echo -e "cargo clippy: ........ ${RED}nok${NC}"
    echo -e "${RED}Pre-commit: Issues detected by clippy."
    exit 1
fi

echo -e "cargo clippy: ........ ${GREEN}ok${NC}"

# cargo test hook
if ! cargo test
then
    echo -e "cargo test: .......... ${RED}nok${NC}" 
    echo -e "${RED}Pre-commit: Issues were detected when running the test suite." 
    exit 1
fi

echo -e "cargo test: .......... ${GREEN}ok${NC}"

echo -e "\n${GREEN}${BOLD}Success: ${NC}${NORM}All pre-commit checks passed ✅\n"

exit 0
#!/bin/sh

set -eu

# formatting variables
GREEN='\033[0;32m'
RED='\033[0;31m'
NC='\033[0m' # No Color
BOLD=$(tput bold)
NORM=$(tput sgr0)

echo "Running pre-commit checks..."

# cargo check hook
if ! cargo check --workspace
then
    echo -e "cargo check: ......... ${RED}nok${NC}"
    echo -e "${RED}Pre-commit: Issues detected when calling 'cargo check'."
    exit 1
fi

echo -e "cargo check: ......... ${GREEN}ok${NC}"

# cargo rustfmt hook
if ! cargo fmt -- --check
then
    echo -e "cargo rustfmt: ....... ${RED}nok${NC}"
    echo -e "${RED}Pre-commit: Code style issues detected with rustfmt."
    exit 1
fi

echo -e "cargo rustfmt: ....... ${GREEN}ok${NC}"

# cargo clippy hook
if ! cargo clippy --all-targets -- -D warnings
then
    echo -e "cargo clippy: ........ ${RED}nok${NC}"
    echo -e "${RED}Pre-commit: Issues detected by clippy."
    exit 1
fi

echo -e "cargo clippy: ........ ${GREEN}ok${NC}"

echo -e "\n${GREEN}${BOLD}Success: ${NC}${NORM}All pre-commit checks passed ✅\n"

exit 0
Note

The above code in step 2 is inspired by a solution posted in a deaddabe blog. This is built on here by adding additional hooks and CLI formatting.

  1. Make this file executable:
chmod +x .git/hooks/pre-commit
  1. Check the pre-commit hooks are working by calling:
bash .git/hooks/pre-commit
  1. Enjoy using rust with pre-commits 🥳