Codex CLI Rust Developer Guide: Auto-generate Tests, Fix Clippy Errors & Rust Workflow
AGENTS.md in your project root declaring cargo build, cargo clippy -- -D warnings, cargo test, and cargo fmt. Then describe tasks in natural language — Codex generates unit tests, fixes Clippy warnings, resolves borrow-checker errors, and migrates async code. Codex understands Rust's ownership, lifetimes, and borrowing rules and runs cargo itself to verify before handing back. Pasting the full rustc error output yields the most accurate fixes.
Rust is celebrated for memory safety and zero-cost abstractions, but its borrow checker, lifetimes, and strict type system also make developers "fight the compiler" constantly. Codex CLI understands Rust's ownership model deeply: it can batch-generate idiomatic unit tests, precisely fix Clippy warnings, rewrite error handling, and help you migrate to async/await and integrate WebAssembly. This guide covers the complete set of Codex use cases for Rust projects.
1. AGENTS.md Configuration for Rust Projects
Place an AGENTS.md in your project root to tell Codex CLI your build commands, code conventions, and toolchain requirements. Here is a complete example for a production-grade Rust project:
# Rust project conventions
## Build & test
- Always run `cargo build` to verify compilation
- Run tests: `cargo test -- --nocapture`
- Lint: `cargo clippy -- -D warnings`
- Format: `cargo fmt --check`
- Security audit: `cargo audit` (requires cargo-audit)
## Code conventions
- Every unwrap() must be replaced with proper error handling (? operator or match)
- Define custom error types with thiserror
- Use the Tokio runtime for async code
- Avoid clone() — prefer references or Arc
## Project structure
- src/lib.rs core library code
- src/main.rs executable entry point
- tests/ integration tests
- benches/ performance benchmarks
What each section does:
- Build & test: the
--nocaptureflag shows test output in real time for easier debugging;cargo auditchecks for known CVEs and is ideal for CI. - Code conventions: explicitly banning
unwrap()is the single most effective rule — it makes Codex consistently produce robust error handling rather than prototype-grade code. - Project structure: distinguishing
lib.rs(library) frommain.rs(binary) helps Codex place new code correctly, andbenches/hints that Codex can generate criterion benchmarks.
edition = "2021" in rust-toolchain.toml); subdirectory files declare crate-specific dependency constraints.
cargo clippy -- -D warnings to the allowed-commands list is critical. It lets Codex run Clippy immediately after each edit, forming an automatic "edit → verify → fix again" loop that produces far higher-quality code than merely asking for "idiomatic" output in the prompt.
2. Auto-generating Rust Unit Tests
Rust test modules live in the same file as the source via the #[cfg(test)] attribute — a language-level convention. Codex CLI understands this pattern and can analyze function signatures, return types, and doc comments to generate complete test modules covering happy paths, error paths, and edge cases.
2.1 Basic prompts
$ codex "Generate Rust unit tests for parse_config covering edge cases"
$ codex "Generate a test module for every pub fn in src/lib.rs"
2.2 Example generated tests
Here is a typical #[cfg(test)] module Codex generates for parse_config:
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_config_valid() {
let input = r#"{"port": 8080, "host": "localhost"}"#;
let config = parse_config(input).unwrap();
assert_eq!(config.port, 8080);
assert_eq!(config.host, "localhost");
}
#[test]
fn test_parse_config_invalid_json() {
let result = parse_config("not json");
assert!(result.is_err());
}
#[test]
fn test_parse_config_missing_field() {
let input = r#"{"port": 8080}"#;
let result = parse_config(input);
assert!(result.is_err());
}
}
2.3 Batch-generating tests for a whole module
$ codex "Scan src/lib.rs and generate a #[cfg(test)] module for every pub fn:
- Each function needs at least four cases: normal input, empty/zero value, boundary value, error path
- Error paths use assert!(result.is_err()) or matches!(err, MyError::...)
- For functions returning Result, test both Ok and Err variants
- Name test functions test_{function_name}_{scenario}"
2.4 Property testing
$ codex "Add proptest property tests for serialize_and_deserialize,
verifying that any valid input round-trips (serialize then deserialize equals original)"
cargo tarpaulin --out Html (requires cargo-tarpaulin) to generate a coverage report, find uncovered branches, then have Codex add the matching test cases. Prompt format: "The following lines are not covered by tests: [paste uncovered code]. Add the corresponding test cases."
3. Fixing Clippy Errors
Clippy is Rust's official lint tool with over 700 built-in code-quality rules. Codex CLI understands all the common Clippy lints and can batch-fix warnings while preserving each function's original behavior.
3.1 Feeding Clippy output to Codex
# Step 1: collect Clippy warnings
$ cargo clippy 2&1 | head -60
# Step 2: paste the output into Codex
$ codex "Fix the following Clippy warnings without changing behavior: [paste output]"
# Or pipe it directly (interactive mode)
$ cargo clippy 2&1 | codex "Fix these Clippy warnings"
3.2 Common Clippy lints and Codex fix prompts
| Lint | Typical issue | Codex fix prompt |
|---|---|---|
clippy::unwrap_used |
.unwrap() panics directly |
"Replace all unwrap() with ?, change the signature to return Result" |
clippy::clone_on_ref_ptr |
Unnecessary Arc clone | "Find avoidable Arc clones and pass by reference instead" |
clippy::too_many_arguments |
More than 7 function arguments | "Extract related arguments into a Config struct; the function takes a struct reference" |
clippy::cognitive_complexity |
Function is too complex | "Split the function into smaller helpers, each doing one thing" |
clippy::needless_pass_by_value |
Argument could be a reference | "Change by-value arguments to &str or &T references" |
clippy::match_wildcard_for_single_variants |
match uses a _ catch-all | "Expand the _ wildcard and handle each enum variant explicitly" |
clippy::redundant_closure |
|x| foo(x) can be simplified |
"Replace the redundant closure with a function pointer or method reference" |
clippy::use_self |
Type name used instead of Self in impl | "Replace the concrete type name with Self inside the impl block" |
3.3 Batch-fixing the whole repo
$ codex "Run cargo clippy -- -D warnings and fix all warnings until clippy outputs nothing"
clippy::pedantic or clippy::nursery) may conflict with your team's style. Use #![allow(clippy::...)] in AGENTS.md to explicitly exempt rules that don't apply, so Codex doesn't make unwanted changes.
4. Fixing Ownership & Borrow Errors
Rust's borrow checker is the biggest hurdle for newcomers. Codex CLI can interpret rustc error output and, combined with code context, infer the most appropriate fix — adding a reference, introducing Clone, or using RefCell/Mutex for interior mutability.
4.1 A general prompt for borrow errors
$ codex "Fix the borrow-checker error in this Rust code and explain why:
[paste the rustc error output and the relevant code]"
4.2 Common ownership errors and fix strategies
Move error (value used after being moved)
// Error: value moved here
let s = String::from("hello");
process(s); // s is moved into the function
println!("{}", s); // error: value borrowed here after move
// Codex fix option 1: pass a reference
process(&s);
println!("{}", s); // OK
// Codex fix option 2: Clone (only when necessary)
process(s.clone());
println!("{}", s); // OK
Multiple mutable borrows
// Error: cannot borrow as mutable more than once
let mut v = vec![1, 2, 3];
let first = &mut v[0];
v.push(4); // error: second mutable borrow
// Codex fix: shorten the borrow scope
{
let first = &mut v[0];
*first = 10;
} // first is released here
v.push(4); // OK
4.3 Lifetime annotations
$ codex "Add the correct lifetime annotations to this struct,
which holds a reference to external data:
[paste the struct definition and rustc error]"
// Example struct with lifetime annotations generated by Codex
struct Parser<'a> {
input: &'a str,
cursor: usize,
}
impl<'a> Parser<'a> {
fn new(input: &'a str) -> Self {
Parser { input, cursor: 0 }
}
fn peek(&self) -> Option<&'a str> {
self.input.get(self.cursor..)
}
}
rustc --explain E0505 (replace with your actual error code) for a detailed explanation, then send that explanation together with the code to Codex for a more precise fix.
5. Async Rust (Tokio)
Migrating synchronous code to async is one of the most common refactoring tasks in Rust projects. Codex CLI understands async/await syntax, Tokio runtime configuration, and the Future trait, and can perform the migration automatically while adding matching async tests.
5.1 Sync-to-async prompts
$ codex "Convert this synchronous HTTP client to the async version of reqwest,
change signatures to async fn and add the appropriate .await calls"
$ codex "Convert all database operations in src/db.rs to sqlx's async interface,
keeping the existing error-handling logic unchanged"
5.2 Configuring Tokio in AGENTS.md
## Async runtime
- Use Tokio as the async runtime (tokio = { features = ["full"] })
- main uses the #[tokio::main] attribute
- Tests use #[tokio::test]
- Avoid std::thread::sleep in async contexts; use tokio::time::sleep
5.3 Async test examples
#[tokio::test]
async fn test_fetch_user() {
let client = UserClient::new("http://test-server");
let user = client.fetch(42).await.unwrap();
assert_eq!(user.id, 42);
}
#[tokio::test]
async fn test_concurrent_requests() {
let client = UserClient::new("http://test-server");
let (user1, user2) = tokio::join!(
client.fetch(1),
client.fetch(2)
);
assert!(user1.is_ok());
assert!(user2.is_ok());
}
5.4 Timeouts and cancellation
$ codex "Add tokio::time::timeout to all external HTTP calls,
read the timeout from config, and return a custom TimeoutError on timeout"
6. Modernizing Error Handling (thiserror / anyhow)
Older Rust code often uses Box<dyn Error> or hand-written impl std::error::Error, which is hard to maintain in large projects. Codex CLI can migrate such code to modern error handling: thiserror for library code, anyhow for application code.
6.1 Migration prompts
$ codex "Rewrite this Error enum with thiserror and add proper Display impls:
[paste the existing Error enum]"
$ codex "Change error handling in src/main.rs to use anyhow::Result,
preserving all error context (.context())"
6.2 Before and after
Before (hand-written impl):
use std::fmt;
#[derive(Debug)]
pub enum AppError {
IoError(std::io::Error),
ParseError(String),
NotFound,
}
impl fmt::Display for AppError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
AppError::IoError(e) => write!(f, "IO error: {}", e),
AppError::ParseError(s) => write!(f, "Parse error: {}", s),
AppError::NotFound => write!(f, "Not found"),
}
}
}
impl std::error::Error for AppError {}
After (thiserror):
use thiserror::Error;
#[derive(Debug, Error)]
pub enum AppError {
#[error("IO error: {0}")]
IoError(#[from] std::io::Error),
#[error("Parse error: {0}")]
ParseError(String),
#[error("Not found")]
NotFound,
}
6.3 Using anyhow at the application layer
use anyhow::{Context, Result};
fn load_config(path: &str) -> Result<Config> {
let content = std::fs::read_to_string(path)
.with_context(|| format!("Failed to read config file: {}", path))?;
let config: Config = serde_json::from_str(&content)
.context("Failed to parse config JSON")?;
Ok(config)
}
thiserror to return precise error types callers can match on; application crates use anyhow to cut boilerplate, with errors carrying a full context chain. State your choice in AGENTS.md to avoid mixing the two.
7. WebAssembly (wasm-bindgen)
Rust is one of the most mature languages in the WebAssembly ecosystem. Codex CLI can generate wasm-bindgen bindings for an existing Rust library so JavaScript can call Rust functions directly, with no hand-written glue code.
7.1 Generating wasm-bindgen bindings
$ codex "Generate a wasm-bindgen interface for this Rust library
so JavaScript can call parse_csv,
with the return value converted to a JavaScript Array"
7.2 wasm-bindgen attribute usage
use wasm_bindgen::prelude::*;
// Export to JavaScript
#[wasm_bindgen]
pub fn parse_csv(input: &str) -> Result<JsValue, JsValue> {
let records = csv_parser::parse(input)
.map_err(|e| JsValue::from_str(&e.to_string()))?;
Ok(serde_wasm_bindgen::to_value(&records)?)
}
// Export a struct
#[wasm_bindgen]
pub struct CsvParser {
delimiter: char,
}
#[wasm_bindgen]
impl CsvParser {
#[wasm_bindgen(constructor)]
pub fn new(delimiter: char) -> Self {
CsvParser { delimiter }
}
pub fn parse(&self, input: &str) -> Result<JsValue, JsValue> {
// implementation...
todo!()
}
}
7.3 Build commands
# Install wasm-pack
$ cargo install wasm-pack
# Build a WASM package for the web
$ wasm-pack build --target web --out-dir pkg
# Build a package for Node.js
$ wasm-pack build --target nodejs --out-dir pkg-node
7.4 Have Codex generate TypeScript type declarations
$ codex "Based on the .d.ts file generated by wasm-bindgen,
add detailed JSDoc comments and type descriptions for parse_csv and CsvParser"
8. GitHub Actions CI for Rust
A complete Rust CI pipeline needs to cover compilation checks, tests, formatting, Clippy, and security audits. Codex CLI can generate a GitHub Actions workflow optimized for Rust, including Swatinem/rust-cache for faster builds.
8.1 Prompt to generate the CI config
$ codex "Generate a GitHub Actions workflow for a Rust project:
- Trigger on push and pull_request
- Run cargo build, cargo test, cargo clippy, cargo fmt --check
- Use rust-cache to cache dependencies
- Add a cargo audit security-check job"
8.2 Complete workflow example
name: Rust CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- uses: Swatinem/rust-cache@v2
- run: cargo build --verbose
- run: cargo test --verbose
- run: cargo clippy -- -D warnings
- run: cargo fmt --check
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- run: cargo install cargo-audit
- run: cargo audit
8.3 Matrix testing multiple Rust versions
$ codex "Modify the CI workflow to test stable and beta toolchains with a matrix strategy,
and add an MSRV (minimum supported Rust version) check"
strategy:
matrix:
rust: [stable, beta, "1.70"] # 1.70 = MSRV
steps:
- uses: dtolnay/rust-toolchain@${{ matrix.rust }}
dtolnay/rust-toolchain Action is the community-maintained standard for installing the toolchain — lighter and more stable than actions-rs/toolchain. Swatinem/rust-cache caches ~/.cargo/registry and target/, cutting CI time from ~5 minutes to under 1 minute.
9. Frequently Asked Questions
Does Codex understand Rust's ownership system?
Yes. Codex CLI deeply understands Rust's ownership, borrowing, and lifetime model. It can accurately diagnose borrow-checker errors and suggest appropriate fixes — references, clones, RefCell, or Arc. Pasting the full rustc error output (including error codes like E0505) yields the most accurate fixes. For complex lifetime issues, include the call chain to help Codex understand the data flow.
How do I make Codex generate idiomatic Rust code?
Spell it out in AGENTS.md: propagate errors with the ? operator, define custom Error enums with thiserror, avoid unnecessary clone(), prefer iterator chains over manual for loops, and use impl Trait instead of Box<dyn Trait>. Once cargo clippy -- -D warnings is on the allowed-commands list, Codex auto-validates after every edit and consistently produces idiomatic code.
Can Codex write Rust macros?
Yes. Codex CLI can write declarative macros (macro_rules!) and procedural macros (proc macros), and understands the difference between derive, attribute, and function-like macros. For complex proc macros, provide an example of the expected expansion in your prompt to help Codex understand the semantic boundaries. Use cargo expand (cargo-expand required) to verify the expansion, then feed the output back to Codex for corrections.
How does Codex handle unsafe Rust?
Codex CLI is conservative with unsafe code and prefers safe alternatives. When unsafe is genuinely required (FFI bindings, low-level memory operations), Codex adds // Safety: comments documenting the invariants and suggests verifying with Miri. Defining your unsafe policy in AGENTS.md (e.g. allowed only in specific modules) keeps Codex seeking safe alternatives everywhere else.