SKILL.md
Rust Standards
Standards for Rust code.
Edition
- Use the latest stable edition (2021+)
- Set
editionexplicitly inCargo.toml
Toolchain
- Follow the
lintskill for formatting and linting — lintro runsrustfmt,clippy,
cargoaudit, and cargodeny as configured
- Treat clippy warnings as errors in CI (
-D warnings) - Customize formatting via
rustfmt.tomlwhere needed
Error Handling
- Use
thiserrorfor library error types — derive structured, typed errors - Use
anyhowfor application-level error propagation - No
.unwrap()in library code — use?or returnResult .expect()only with descriptive messages explaining the invariant:
```rust // Good let config = load_config().expect("config.toml must exist at startup");
// Bad let config = load_config().unwrap(); ```
- Implement
std::fmt::Displayfor all custom error types - Non-panicking
.unwrapordefault()/.unwrap_or()over trivial-arm matches:
```rust // Don't let count = match maybe_count { Some(n) => n, None => 0, };
// Do let count = maybecount.unwrapor(0); ```
Type Patterns
- Prefer newtypes for domain concepts —
struct UserId(u64)over bareu64 - Use
impl Traitin argument position for flexibility; explicit generics in return
position for clarity
- Derive
Debugon all public types - Derive
Clone,PartialEq,Eq,Hashwhere semantically appropriate - Prefer
&stroverStringin function arguments; returnStringwhen ownership
transfers
Unsafe
unsafeblocks MUST include a// SAFETY:comment justifying soundness:
``rust // SAFETY: pointer is guaranteed non-null by the allocator contract, // and the lifetime is bounded by the enclosing scope. unsafe { ptr.as_ref() } ``
- Minimize unsafe surface area — encapsulate in safe abstractions
- Prefer safe alternatives (e.g.,
std::sync::Mutexover raw atomics) unless
performance demands otherwise
Documentation
///doc comments on all public items (functions, types, traits, modules)- Include code examples in doc comments for non-trivial APIs:
``rust /// Parse a duration string like "5s", "100ms", or "2m". /// /// # Examples /// /// ` /// use mycrate::parseduration; /// /// let d = parseduration("5s").unwrap(); /// asserteq!(d, std::time::Duration::fromsecs(5)); /// ` pub fn parse_duration(s: &str) -> Result<Duration> { ... } ``
- Use
#![deny(missing_docs)]for library crates - Module-level
//!doc comments for crate and module overviews
Testing
- Unit tests in
#[cfg(test)] mod testswithin the same file - Integration tests in
tests/directory - Use
#[should_panic(expected = "...")]for expected panics - Consider
proptestorquickcheckfor property-based testing where valuable - Use
asserteq!andassertne!over bareassert!for better error messages
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_duration_seconds() {
let d = parse_duration("5s").unwrap();
assert_eq!(d, Duration::from_secs(5));
}
#[test]
#[should_panic(expected = "invalid format")]
fn parse_duration_rejects_garbage() {
parse_duration("not_a_duration").unwrap();
}
}
Dependencies
- Keep the dependency tree minimal — every dependency is an audit and supply chain
surface
- Run vulnerability scanning via lintro (
uv run lintro chkincludescargo_audit
and cargo_deny)
- Pin versions in workspace
Cargo.tomlfor multi-crate workspaces - Prefer well-maintained crates with active maintainers and good documentation
Patterns
- Prefer
implblocks over free functions for associated behavior - Use the builder pattern for types with many optional fields
- Prefer iterators and combinators over manual loops where readability permits
let-elseover nestedif letpyramids:
```rust // Don't if let Some(user) = lookup(id) { if let Some(email) = user.email { send(email); } }
// Do let Some(user) = lookup(id) else { return }; let Some(email) = user.email else { return }; send(email); ```
.find()/.position()/.any()over manual index loops:
```rust // Don't let mut idx = None; for (i, item) in items.iter().enumerate() { if item.id == target { idx = Some(i); break; } }
// Do let idx = items.iter().position(|item| item.id == target); ```
matches!()for pattern booleans:
```rust // Don't let isready = match state { State::Ready => true, => false, };
// Do let is_ready = matches!(state, State::Ready); ```
- Use
#[must_use]on functions whose return value should not be ignored - Prefer
From/Intoimplementations over ad-hoc conversion methods
Linting
Follow the lint skill for linting and formatting workflow.