Skip to content

check_rust_version

rhiza_hooks.check_rust_version

Check that the Rust version is consistent across project files.

A Rust project states its version in up to three places:

  • rust-toolchain.toml[toolchain] channel, the toolchain rustup installs for this checkout;
  • rust-toolchain — the legacy form of the same file, either TOML or a bare channel name on a single line;
  • Cargo.tomlrust-version under [package] and/or [workspace.package], the crate's minimum supported Rust version (MSRV).

The hook enforces that the two toolchain files agree with each other, that the two MSRV declarations agree with each other, and that the pinned toolchain is not older than the declared MSRV (a pin below the MSRV cannot build the crate). Named channels (stable, beta, nightly-2024-01-01) carry no version number, so they are accepted without comparison.

check_version_consistency(repo_root)

Check Rust version consistency across project files.

Parameters:

Name Type Description Default
repo_root Path

Root directory of the repository.

required

Returns:

Type Description
list[str]

List of error messages (empty if consistent, or if the repository

list[str]

declares no Rust versions at all).

Source code in rhiza_hooks/check_rust_version.py
def check_version_consistency(repo_root: Path) -> list[str]:
    """Check Rust version consistency across project files.

    Args:
        repo_root: Root directory of the repository.

    Returns:
        List of error messages (empty if consistent, or if the repository
        declares no Rust versions at all).
    """
    channels = get_toolchain_channels(repo_root)
    msrvs = get_cargo_rust_versions(repo_root)

    return [
        *_check_channels_agree(channels),
        *_check_msrvs_agree(msrvs),
        *_check_channel_satisfies_msrv(channels, msrvs),
    ]

get_cargo_rust_versions(repo_root)

Collect the MSRVs declared in Cargo.toml.

Parameters:

Name Type Description Default
repo_root Path

Root directory of the repository.

required

Returns:

Type Description
dict[str, str]

Mapping of table label (package / workspace.package) to the

dict[str, str]

rust-version string declared there.

Source code in rhiza_hooks/check_rust_version.py
def get_cargo_rust_versions(repo_root: Path) -> dict[str, str]:
    """Collect the MSRVs declared in ``Cargo.toml``.

    Args:
        repo_root: Root directory of the repository.

    Returns:
        Mapping of table label (``package`` / ``workspace.package``) to the
        ``rust-version`` string declared there.
    """
    data = _load_toml(repo_root / CARGO_FILE)
    if data is None:
        return {}

    versions: dict[str, str] = {}
    for label, keys in _MSRV_TABLES.items():
        value = _string_value(_table(data, *keys), "rust-version")
        if value is not None:
            versions[label] = value
    return versions

get_toolchain_channels(repo_root)

Collect the pinned toolchain channels declared in the repository.

Parameters:

Name Type Description Default
repo_root Path

Root directory of the repository.

required

Returns:

Type Description
dict[str, str]

Mapping of filename to channel string, containing only the files that

dict[str, str]

exist and actually declare a channel.

Source code in rhiza_hooks/check_rust_version.py
def get_toolchain_channels(repo_root: Path) -> dict[str, str]:
    """Collect the pinned toolchain channels declared in the repository.

    Args:
        repo_root: Root directory of the repository.

    Returns:
        Mapping of filename to channel string, containing only the files that
        exist and actually declare a channel.
    """
    channels: dict[str, str] = {}

    data = _load_toml(repo_root / TOOLCHAIN_FILE)
    if data is not None:
        channel = _string_value(_table(data, "toolchain"), "channel")
        if channel is not None:
            channels[TOOLCHAIN_FILE] = channel

    legacy = read_legacy_toolchain(repo_root / LEGACY_TOOLCHAIN_FILE)
    if legacy is not None:
        channels[LEGACY_TOOLCHAIN_FILE] = legacy

    return channels

main(argv=None)

Main entry point for the hook.

Source code in rhiza_hooks/check_rust_version.py
def main(argv: list[str] | None = None) -> int:
    """Main entry point for the hook."""
    parser = argparse.ArgumentParser(description="Check Rust version consistency")
    parser.add_argument(
        "filenames",
        nargs="*",
        help="Filenames (ignored, checks repo root)",
    )
    parser.parse_args(argv)  # validate/consume pre-commit's filename args; result unused

    repo_root = find_repo_root()
    errors = check_version_consistency(repo_root)

    if errors:
        for error in errors:
            print(f"ERROR: {error}")
        return 1

    return 0

read_legacy_toolchain(path)

Read the channel from a legacy rust-toolchain file.

rustup accepts either the modern TOML form or a bare channel name, so TOML is tried first and plain text is the fallback.

Parameters:

Name Type Description Default
path Path

Path to the rust-toolchain file.

required

Returns:

Type Description
str | None

The channel string, or None if the file is missing, unreadable, or

str | None

declares no channel.

Source code in rhiza_hooks/check_rust_version.py
def read_legacy_toolchain(path: Path) -> str | None:
    """Read the channel from a legacy ``rust-toolchain`` file.

    rustup accepts either the modern TOML form or a bare channel name, so TOML
    is tried first and plain text is the fallback.

    Args:
        path: Path to the ``rust-toolchain`` file.

    Returns:
        The channel string, or None if the file is missing, unreadable, or
        declares no channel.
    """
    if not path.exists():
        return None
    try:
        text = path.read_text(encoding="utf-8")
    except (OSError, UnicodeDecodeError):
        return None

    stripped = text.strip()
    if not stripped:
        return None

    try:
        data = tomllib.loads(stripped)
    except tomllib.TOMLDecodeError:
        # Not TOML: the whole file is the channel name (the legacy format).
        return stripped

    return _string_value(_table(data, "toolchain"), "channel")