Rust project workflow guidelines. Activate when working with Rust files (.rs), Cargo.toml, Cargo.lock, or Rust-specific tooling.
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.
| Task | Tool | Command |
|------|------|---------|
| Lint | Clippy | cargo clippy |
| Format | rustfmt | cargo fmt |
| Type check | built-in | cargo check |
| Build | cargo | cargo build |
| Test | cargo | cargo test |
| Security | cargo-audit | cargo audit |
| Coverage | cargo-tarpaulin | cargo tarpaulin |
| Docs | rustdoc | cargo doc |
# Check before commit
cargo fmt --check && cargo clippy -- -D warnings && cargo test
# Full validation
cargo fmt && cargo clippy --fix --allow-dirty && cargo test && cargo doc --no-deps
cargo build --release
cargo test --release
All public types MUST implement:
Debug - Required for error messages and debuggingClone - Required unless the type explicitly manages unique resourcesPublic types SHOULD implement:
PartialEq - When equality comparison is meaningfulEq - When PartialEq is reflexive (most cases)Hash - When the type may be used as a keyDefault - When a sensible default exists// Minimal public struct
#[derive(Debug, Clone)]
pub struct Config { /* ... */ }
// Value type (data container)
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct UserId(String);
// With default
#[derive(Debug, Clone, Default, PartialEq)]
pub struct Settings { /* ... */ }
// Serializable types
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct ApiResponse { /* ... */ }
Types representing user-facing values SHOULD implement Display:
use std::fmt;
impl fmt::Display for UserId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
Cargo.lock to version control.gitignore exceptions if using a global ignoreCargo.lock (add to .gitignore)Cargo.lockthiserroruse thiserror::Error;
#[derive(Debug, Error)]
pub enum LibraryError {
#[error("invalid configuration: {0}")]
InvalidConfig(String),
#[error("resource not found: {resource}")]
NotFound { resource: String },
#[error(transparent)]
Io(#[from] std::io::Error),
}
anyhowuse anyhow::{Context, Result};
fn main() -> Result<()> {
let config = load_config()
.context("failed to load configuration")?;
run_app(config)?;
Ok(())
}
.unwrap() in library code (except tests).expect() without a meaningful message? operator for propagation.context() or .with_context().unwrap() in main() for unrecoverable errorsLibraries SHOULD define a Result alias:
pub type Result<T> = std::result::Result<T, LibraryError>;
&T) allowed simultaneously&mut T) at a time// Explicit lifetime when returning borrowed data
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
// Struct containing references
struct Parser<'a> {
input: &'a str,
}
// Clone to avoid borrow issues (acceptable for small types)
let owned = borrowed.to_string();
// Use Cow for flexible ownership
use std::borrow::Cow;
fn process(input: Cow<'_, str>) -> String { /* ... */ }
// Arc/Rc for shared ownership
use std::sync::Arc;
let shared = Arc::new(data);
unsafe blocks&[T] over raw pointers for slicesBox<T> for heap allocationArc<T> or Rc<T> for shared ownershipsrc/
├── lib.rs # Library root (pub mod declarations)
├── main.rs # Binary entry point (optional)
├── config.rs # Single-file module
├── handlers/ # Multi-file module
│ ├── mod.rs # Module root (pub mod + re-exports)
│ ├── auth.rs # Submodule
│ └── api.rs # Submodule
└── utils/
├── mod.rs
└── helpers.rs
// src/handlers/mod.rs
mod auth;
mod api;
// Re-export public items
pub use auth::AuthHandler;
pub use api::{ApiClient, ApiError};
// Keep private implementation details
use auth::internal_helper;
pub only for intentional public APIpub(crate) for crate-internal itemspub(super) for parent-module accesspub(in path) for fine-grained control// src/prelude.rs
pub use crate::config::Config;
pub use crate::error::{Error, Result};
pub use crate::traits::*;
Unit tests MUST be in the same file as the code being tested:
// src/calculator.rs
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add_positive() {
assert_eq!(add(2, 3), 5);
}
#[test]
fn test_add_negative() {
assert_eq!(add(-1, -1), -2);
}
}
Integration tests MUST be in the tests/ directory:
tests/
├── common/
│ └── mod.rs # Shared test utilities
├── api_tests.rs # Integration test file
└── cli_tests.rs # Another test file
// Use test fixtures
#[test]
fn test_with_fixture() {
let fixture = TestFixture::new();
// test code
}
// Test error conditions
#[test]
fn test_invalid_input() {
let result = parse("");
assert!(result.is_err());
}
// Use should_panic for expected panics
#[test]
#[should_panic(expected = "index out of bounds")]
fn test_panic_condition() {
let v = vec![1, 2, 3];
let _ = v[10];
}
// Run expensive tests only with --ignored
#[test]
#[ignore]
fn expensive_test() { /* ... */ }
// Async tests (with tokio)
#[tokio::test]
async fn async_test() { /* ... */ }
#[ignore] for slow tests/// Calculates the factorial of a number.
///
/// # Arguments
///
/// * `n` - The number to calculate factorial for
///
/// # Returns
///
/// The factorial of `n`, or `None` if overflow occurs.
///
/// # Examples
///
/// ```
/// use mylib::factorial;
/// assert_eq!(factorial(5), Some(120));
/// ```
///
/// # Panics
///
/// This function does not panic.
pub fn factorial(n: u64) -> Option<u64> {
// implementation
}
//! # Authentication Module
//!
//! This module provides authentication and authorization
//! functionality for the application.
//!
//! ## Features
//!
//! - JWT token validation
//! - Role-based access control
//! - Session management
mod jwt;
mod roles;
# Examples for non-trivial functions# Panics if the function can panic# Errors for functions returning Result# Safety for unsafe functions#[doc(hidden)] for public-but-internal itemsExamples in documentation are compiled and run as tests:
/// ```
/// # use mylib::Config; // Hidden setup line
/// let config = Config::default();
/// assert!(config.is_valid());
/// ```
unsafe blocksunsafe in safe abstractionsunsafe for convenience/performance without justification/// Reads a value from the given pointer.
///
/// # Safety
///
/// The caller MUST ensure:
/// - `ptr` is valid and properly aligned
/// - `ptr` points to initialized memory
/// - No other references to this memory exist
pub unsafe fn read_ptr<T>(ptr: *const T) -> T {
// SAFETY: Caller guarantees pointer validity per function contract
unsafe { std::ptr::read(ptr) }
}
pub struct SafeBuffer {
ptr: *mut u8,
len: usize,
}
impl SafeBuffer {
pub fn new(size: usize) -> Self {
let ptr = unsafe {
// SAFETY: size > 0 checked, layout is valid
std::alloc::alloc(std::alloc::Layout::array::<u8>(size).unwrap())
};
Self { ptr, len: size }
}
// Safe public API
pub fn get(&self, index: usize) -> Option<u8> {
if index < self.len {
// SAFETY: bounds checked above
Some(unsafe { *self.ptr.add(index) })
} else {
None
}
}
}
// SAFETY: comment before every unsafe block#[deny(unsafe_op_in_unsafe_fn)]#[derive(Debug, Clone, Default)]
pub struct RequestBuilder {
url: String,
headers: Vec<(String, String)>,
timeout: Option<u64>,
}
impl RequestBuilder {
pub fn new(url: impl Into<String>) -> Self {
Self { url: url.into(), ..Default::default() }
}
pub fn header(mut self, key: &str, value: &str) -> Self {
self.headers.push((key.to_string(), value.to_string()));
self
}
pub fn timeout(mut self, seconds: u64) -> Self {
self.timeout = Some(seconds);
self
}
pub fn build(self) -> Request {
Request { /* ... */ }
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Email(String);
impl Email {
pub fn new(value: &str) -> Result<Self, ValidationError> {
if value.contains('@') {
Ok(Self(value.to_string()))
} else {
Err(ValidationError::InvalidEmail)
}
}
pub fn as_str(&self) -> &str {
&self.0
}
}
pub struct Connection<State> {
inner: TcpStream,
_state: std::marker::PhantomData<State>,
}
pub struct Disconnected;
pub struct Connected;
impl Connection<Disconnected> {
pub fn connect(addr: &str) -> Result<Connection<Connected>, Error> {
let stream = TcpStream::connect(addr)?;
Ok(Connection { inner: stream, _state: std::marker::PhantomData })
}
}
impl Connection<Connected> {
pub fn send(&mut self, data: &[u8]) -> Result<(), Error> { /* ... */ }
}
Add to Cargo.toml or clippy.toml:
# Cargo.toml
[lints.clippy]
pedantic = "warn"
nursery = "warn"
unwrap_used = "deny"
expect_used = "warn"
#[allow(clippy::too_many_arguments)]
fn complex_function(/* many args */) { /* ... */ }
cargo clippy -- -D warnings -D clippy::unwrap_used
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer