This document describes how Verus integrates with Cargo via the cargo-verus
binary. It is intended for developers who want to understand, debug, or extend
this integration.
cargo-verus is a Cargo subcommand that enables verification of Rust code as
part of the normal Cargo workflow. When users run cargo verus verify, Cargo
locates the cargo-verus binary and invokes it as an extension.
Key principle: cargo-verus is a thin wrapper that translates Cargo metadata
and user arguments into environment variables, then invokes cargo build with
the RUSTC_WRAPPER environment variable set to the verus binary. The verus
driver performs the actual verification.
User: cargo verus verify -p my_crate --release -- --rlimit=60
↓
cargo-verus binary
├─ Parses arguments
├─ Queries cargo metadata
├─ Sets environment variables for each package
└─ Invokes: cargo build -p my_crate --release
with RUSTC_WRAPPER=/path/to/verus
↓
Cargo build system
└─ For each crate compilation:
Invokes: /path/to/verus /path/to/rustc [rustc-args]
↓
verus binary (rust_verify driver)
├─ Reads environment variables
├─ Decides whether to verify or pass through to rustc
├─ If verifying: runs verification pipeline
└─ Exports .vir file for dependents
File: cargo-verus/src/main.rs
The main() function:
- Detects whether invoked as
cargo-verusorcargo verus - Extracts arguments after the subcommand name
- Routes to
process()which creates aVerusCmdand executes it
File: rust_verify/src/main.rs
When invoked via cargo:
- Detects
VERUS_DRIVER_VIA_CARGO=1environment variable - Expects first argument to be path to
rustc(perRUSTC_WRAPPERprotocol) - Calls
extend_args_and_check_is_direct_rustc_call()to unpack environment variables - Decides whether to verify based on
VERUS_DRIVER_VERIFY_{package_id}environment variable
Arguments are split into two categories:
- Cargo arguments (before
--): Passed to cargo (e.g.,--release,-p crate_name,--features) - Verus arguments (after
--): Passed to verus driver (e.g.,--rlimit=60,--expand-errors)
Example:
cargo verus verify -p my_crate --release -- --rlimit=60 --expand-errors
# ^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^
# Cargo arguments Verus argumentsEnvironment variables are the primary communication mechanism between cargo-verus and the verus driver. This approach is necessary because:
- Cargo's
RUSTC_WRAPPERinvokes verus as a subprocess - Environment variables pass naturally to subprocesses
- They enable per-package configuration
| Variable | Purpose | Set By | Read By |
|---|---|---|---|
VERUS_DRIVER_VIA_CARGO |
Indicates cargo-verus invocation | cargo-verus | verus driver |
VERUS_DRIVER_ARGS |
Common Verus args for all packages | cargo-verus | verus driver |
VERUS_DRIVER_ARGS_FOR_{id} |
Per-package Verus args | cargo-verus | verus driver |
VERUS_DRIVER_VERIFY_{id} |
"1" if package should be verified | cargo-verus | verus driver |
VERUS_DRIVER_IS_BUILTIN_{id} |
"1" if package is verus_builtin |
cargo-verus | verus driver |
VERUS_DRIVER_IS_BUILTIN_MACROS_{id} |
"1" if package is builtin_macros |
cargo-verus | verus driver |
RUSTC_WRAPPER |
Path to verus binary | cargo-verus | cargo |
__CARGO_DEFAULT_LIB_METADATA |
Trigger cargo rebuild when exported .vir file changes | cargo-verus | cargo |
Package IDs are generated to be unique across workspaces:
fn mk_package_id(name, version, manifest_path) -> String {
let hash = SHA256(manifest_path)[0:12];
format!("{}-{}-{}", name, version, hash)
}Arguments are packed into environment variables using a separator:
VERUS_DRIVER_ARGS_SEP = "__VERUS_DRIVER_ARGS_SEP__"
Format: [SEP, arg1, SEP, arg2, ...]
Example:
__VERUS_DRIVER_ARGS_SEP__--rlimit=60__VERUS_DRIVER_ARGS_SEP__--expand-errors
This preserves arguments containing spaces or special characters.
Packages opt into verification via [package.metadata.verus]:
[package.metadata.verus]
verify = true| Field | Type | Default | Purpose |
|---|---|---|---|
verify |
bool | false | Enable verification for this crate |
no_vstd |
bool | false | Don't import vstd |
is_vstd |
bool | false | This crate is vstd itself |
is_core |
bool | false | This crate is the core library |
is_builtin |
bool | false | This crate is verus_builtin |
is_builtin_macros |
bool | false | This crate is builtin_macros |
The VerusMetadata struct is deserialized from the package.metadata.verus
JSON object in Cargo's metadata output.
cargo-verus builds an index of all packages and their dependencies:
struct MetadataIndex<'a> {
entries: BTreeMap<&'a PackageId, MetadataIndexEntry<'a>>,
}
struct MetadataIndexEntry<'a> {
package: &'a Package,
verus_metadata: VerusMetadata,
deps: BTreeMap<&'a str, &'a NodeDep>,
}For each verified package, cargo-verus:
- Iterates its dependencies
- Checks if each dependency has
verify = true - Adds
--VIA-CARGO import-dep-if-present={dep_name}to that package's args
This tells the verus driver which dependencies may have .vir files to import.
When a package is verified, its VIR (Verification Intermediate Representation) is exported:
Location: Same directory as .rlib, with .vir extension, e.g.,
target/debug/deps/libmy_crate-xxxxx.rlib
target/debug/deps/libmy_crate-xxxxx.vir
Process (in rust_verify/src/import_export.rs):
- Prune AST to exported items only
- Remove function bodies for non-public spec functions
- Serialize with
bincode
When verifying a package with dependencies:
- The
handle_externs()function receives the list of external crates from rustc - For each crate in
import_dep_if_present:- Locates the
.rlibfile - Checks if a corresponding
.virfile exists - If found, adds it to the import list
- Locates the
- The verifier deserializes and merges imported VIR
In cargo-verus/src/main.rs, we run:
cmd.env("__CARGO_DEFAULT_LIB_METADATA", "verus");This ensures that Cargo's fingerprinting system detects when .vir files
change, triggering rebuilds of dependent crates.
-
cargo-verus main()
- Parse arguments:
["verify", "-p", "my_crate"] - Create
VerusCmdwithcargo_args = ["-p", "my_crate"],verify = true
- Parse arguments:
-
VerusCmd::into_std_cmd()
- Query cargo metadata for all packages
- Build
MetadataIndex - For each package with
verify = true:- Set
VERUS_DRIVER_VERIFY_{id} = "1" - For each verified dependency, add import-dep-if-present arg
- Set
- Set
RUSTC_WRAPPER = /path/to/verus - Return
cargo build -p my_cratecommand
-
Cargo execution
- Cargo resolves dependencies and build order
- For each crate compilation, invokes
RUSTC_WRAPPER
-
verus driver invocation (per crate)
- Detect
VERUS_DRIVER_VIA_CARGO = "1" - Unpack
VERUS_DRIVER_ARGSandVERUS_DRIVER_ARGS_FOR_{id} - Check
VERUS_DRIVER_VERIFY_{id} - If verifying:
- Import dependency VIR files
- Run verification pipeline
- Export VIR file
- Else: pass through to rustc
- Detect
- Faster than file I/O for each rustc invocation
- Automatic cleanup when cargo process exits
- Avoids file locking issues on parallel builds
- Allows different flags for different crates (e.g.,
--is-vstdonly for vstd) - Avoids unnecessary rebuilds when common args change
- Scales to large workspaces
- Preserves arguments with spaces:
--smt-options="foo bar" - Prevents shell interpretation issues
- Works reliably across platforms
- Automatic cleanup with
cargo clean - Partitioned per build variant (debug/release)
- Integrated with Cargo's fingerprinting
- Add field to
VerusMetadatastruct incargo-verus/src/main.rs - The field will be automatically parsed from
[package.metadata.verus] - Use the field when setting environment variables in
VerusCmd::into_std_cmd() - Read the environment variable in verus driver
- Add constant in
cargo-verus/src/main.rs - Add matching constant in
rust_verify/src/cargo_verus.rs - Set the variable in
VerusCmd::into_std_cmd() - Read in
extend_args_and_check_is_direct_rustc_call()orhandle_externs()
- Add to match in
VerusCmd::new()(around line 202) - Implement command-specific logic
- Update help text
- Run with
CARGO_LOG=cargo::ops::cargo_compile=tracefor cargo debug output - Print environment variables set by cargo-verus
- Run verus driver directly with same environment to isolate issues
- Check
.virfile locations in target directory
| File | Purpose |
|---|---|
cargo-verus/src/main.rs |
Main cargo-verus binary, argument parsing, metadata handling |
rust_verify/src/main.rs |
Verus driver entry point, RUSTC_WRAPPER detection |
rust_verify/src/cargo_verus.rs |
Environment variable unpacking, extern handling |
rust_verify/src/cargo_verus_dep_tracker.rs |
Dependency tracking for incremental builds |
rust_verify/src/import_export.rs |
VIR serialization/deserialization |
rust_verify/src/config.rs |
Argument parsing including cargo-specific args |