rust
Idiomatic programming style, patterns, and conventions for Rust development. Trigger when: - Writing, refactoring, reviewing, or debugging Rust code. - Files matching the pattern **/*.rs (including Ca
SKILL.mdskills/rust/SKILL.md
GitHubRust Language Idioms
Work with the language, not against it. Embrace ownership, the borrow checker, and the type system.
Core Values
- Safety: Lean on the compiler's guarantees. Avoid
unsafeunless absolutely necessary. - Memory Efficiency: Zero-cost abstractions. Avoid unnecessary allocations.
- Ergonomics: APIs should be intuitive and hard to misuse.
API Design
Naming Conventions
| Prefix | Semantics | Cost | Example |
|---|---|---|---|
as_ |
Borrowed view, no allocation | Cheap | as_ref(), as_slice() |
to_ |
Creates new owned value | Expensive | to_string(), to_vec() |
into_ |
Consumes self, transforms | Cheap | into_iter(), into_inner() |
Argument Flexibility
Accept generic bounds instead of concrete types:
// ❌ Inflexible
fn read_file(path: &PathBuf) -> Result<String>
// ✅ Accepts &str, String, PathBuf, &Path
fn read_file(path: impl AsRef<Path>) -> Result<String>
Common patterns:
impl AsRef<Path>— for pathsimpl AsRef<str>— for string-like typesimpl Into<String>— when ownership is needed
Constructors
- Simple:
T::new()for basic instantiation - Configured:
T::with_capacity(n)for single-option variants - Complex: Use the Builder pattern for 3+ optional fields
let config = ConfigBuilder::new()
.port(8080)
.timeout(Duration::from_secs(30))
.build()?;
Monomorphization Control
For widely-used generic functions, consider splitting:
// Public generic wrapper (thin)
pub fn process(path: impl AsRef<Path>) -> Result<()> {
process_inner(path.as_ref())
}
// Private concrete implementation (bulk of logic)
fn process_inner(path: &Path) -> Result<()> {
// ...
}
This reduces binary bloat and compile times.
Error Handling
The ? Operator
Use ? to propagate errors. It:
- Unwraps
Ok(value)or returnsErr(e)early - Automatically converts via
Fromtrait
Library vs. Application Errors
| Context | Approach | Crate |
|---|---|---|
| Libraries | Custom enum error types | thiserror |
| Applications | Boxed errors with context | anyhow |
// Library: structured, matchable errors
#[derive(Debug, thiserror::Error)]
pub enum ParseError {
#[error("invalid header at byte {0}")]
InvalidHeader(usize),
#[error("unexpected EOF")]
UnexpectedEof,
}
// Application: rich context chains
fn main() -> anyhow::Result<()> {
let config = load_config()
.context("failed to load configuration")?;
Ok(())
}
Parse, Don't Validate
Encode invariants in the type system:
// ❌ Stringly-typed, requires repeated validation
fn send_email(to: &str) -> Result<()>
// ✅ Invalid states are unrepresentable
fn send_email(to: EmailAddress) -> Result<()>
The constructor for EmailAddress validates once; all downstream code operates with a compile-time guarantee.
Iterators
Iterators are lazy and zero-cost. Build declarative pipelines:
let result: Vec<_> = items
.iter()
.filter(|x| x.is_valid())
.map(|x| x.transform())
.collect();
Key Adapters
| Adapter | Purpose |
|---|---|
map |
Transform each element |
filter |
Keep elements matching predicate |
filter_map |
Filter and transform in one step |
flat_map |
Map then flatten nested iterators |
zip |
Combine two iterators into pairs |
chain |
Concatenate iterators |
take / skip |
Limit or offset elements |
inspect |
Debug: observe without modifying |
Pitfalls
- Iterators are lazy—no work until consumed (
collect,sum,for_each) - Avoid side effects in
map; usefor_eachor explicit loops - Use
inspect()to debug intermediate pipeline stages
Concurrency
Thread Safety Markers
| Trait | Meaning |
|---|---|
Send |
Ownership can transfer between threads |
Sync |
References (&T) can be shared between threads |
Most types are automatically Send + Sync. Types containing raw pointers or interior mutability may not be.
Common Primitives
Mutex<T>— mutual exclusionRwLock<T>— multiple readers OR single writerArc<T>— atomic reference counting for shared ownershipmpsc::channel— message passing
Anti-Pattern: if let Deadlock (pre-2024 edition)
// ❌ Lock held through else block in older editions
if let Some(x) = mutex.lock().unwrap().get("key") {
// ...
} else {
mutex.lock().unwrap().insert("key", value); // DEADLOCK
}
// ✅ Extract lock explicitly
let guard = mutex.lock().unwrap();
let result = guard.get("key").cloned();
drop(guard); // Release before else
Fixed in Rust 2024 edition.
Type System
Newtypes
Wrap primitives to create distinct types:
struct UserId(u64);
struct PostId(u64);
// Now UserId and PostId are incompatible at compile time
#[must_use]
Apply to functions/types where ignoring the result is almost always a bug:
#[must_use = "this returns a new value and does not modify self"]
fn sorted(&self) -> Vec<T>
impl Trait in Return Position
Hide concrete types when they're implementation details:
fn make_iter() -> impl Iterator<Item = u32> {
(0..10).filter(|x| x % 2 == 0)
}
Anti-Patterns
| Anti-Pattern | Description | Remedy |
|---|---|---|
| Overengineering | Complex abstractions, premature unsafe, macro abuse |
Prefer simple, direct solutions |
| Simplistic Design | Failing to leverage type system (stringly-typed, bool-blind) | Use enums/newtypes to encode invariants |
| Premature Optimization | Optimizing without profiling | Write clear code first; measure, then optimize |
| Documentation Neglect | Missing or stale docs | Doc-comments (///) with examples; run cargo doc |
Blessed Crates
| Domain | Crate | Notes |
|---|---|---|
| Serialization | serde + serde_json |
De facto standard |
| Async Runtime | tokio |
Most widely supported |
| CLI Parsing | clap |
Feature-rich, fast |
| Error (Library) | thiserror |
Derive for custom errors |
| Error (Application) | anyhow |
Context chains, backtraces |
| HTTP Client | reqwest |
Built on tokio |
| Logging | tracing |
Structured, async-aware |
Quick Reference
- No
.unwrap()in library code — propagateResult - No
.clone()without justification — prefer references - No
panic!in library code — return errors - Use
clippy—cargo clippy -- -D warnings - Format with
rustfmt—cargo fmt - Document public APIs —
///with examples
These idioms refine but are subordinate to the Code-Edit Constraints.