| name | idiomatic-rust |
| description | Write idiomatic, high-quality Rust code following community best practices. Use when writing, reviewing, or refactoring Rust code (.rs files, Cargo.toml), converting code to Rust, designing error types with Result/Option, implementing traits, or when discussing ownership, borrowing, lifetimes, or Rust API design patterns. |
Idiomatic Rust
This skill provides guidance for writing idiomatic Rust code that follows community conventions, leverages the type system effectively, and produces maintainable, performant software.
Core Principles
1. Leverage the Type System
Rust's type system is your greatest ally. Use it to:
- Make invalid states unrepresentable - Design types so illegal combinations cannot compile
- Encode invariants at compile time - Use newtypes, enums, and const generics
- Prefer
From/TryFrom over as casts - Explicit, safe conversions
fn process_user(user_id: u64, account_id: u64) { ... }
struct UserId(u64);
struct AccountId(u64);
fn process_user(user_id: UserId, account_id: AccountId) { ... }
2. Embrace Ownership and Borrowing
- Accept borrowed data when possible - Use
&str over String, &[T] over Vec<T> in function parameters
- Return owned data when creating - Let callers decide how to store results
- Use
Cow<'_, T> for flexibility - When you might or might not need to clone
- Prefer
&self methods - Only use &mut self or self when necessary
fn normalize_path(path: &str) -> PathBuf { ... }
fn escape_html(input: &str) -> Cow<'_, str> {
if needs_escaping(input) {
Cow::Owned(do_escape(input))
} else {
Cow::Borrowed(input)
}
}
3. Handle Errors Properly
- Use
Result for recoverable errors, panic! for bugs
- Create meaningful error types - Not just strings
- Don't expose internal error types - Abstract over dependencies
- Use
? liberally - Propagate errors cleanly
pub fn read_config(path: &Path) -> Result<Config, std::io::Error> { ... }
#[derive(Debug, thiserror::Error)]
pub enum ConfigError {
#[error("failed to read config file: {path}")]
Read { path: PathBuf, source: std::io::Error },
#[error("invalid config format")]
Parse(#[from] toml::de::Error),
}
4. Write Expressive Code
- Use iterators over explicit loops - More declarative, often faster
- Leverage pattern matching - Exhaustive, self-documenting
- Prefer standard library types -
Option, Result, Vec, HashMap
- Use
#[must_use] for important return values
let mut results = Vec::new();
for item in items {
if item.is_valid() {
results.push(item.process());
}
}
let results: Vec<_> = items
.iter()
.filter(|item| item.is_valid())
.map(|item| item.process())
.collect();
5. Document with Purpose
- Document the "why", not the "what" - Code shows what, comments explain why
- Include examples in doc comments - They're tested by
cargo test
- Document panics, errors, and safety - Required sections for public APIs
- Use
#[doc(hidden)] for internal public items
pub fn parse_config(path: &str) -> Result<Config, ConfigError> { ... }
Quick Reference
Naming Conventions (RFC 430)
| Item | Convention | Example |
|---|
| Crates | snake_case | my_crate |
| Modules | snake_case | my_module |
| Types | UpperCamelCase | MyStruct |
| Traits | UpperCamelCase | MyTrait |
| Enum variants | UpperCamelCase | MyVariant |
| Functions | snake_case | my_function |
| Methods | snake_case | my_method |
| Local variables | snake_case | my_variable |
| Static variables | SCREAMING_SNAKE_CASE | MY_STATIC |
| Constants | SCREAMING_SNAKE_CASE | MY_CONST |
| Type parameters | UpperCamelCase, short | T, E, K, V |
| Lifetimes | lowercase, short | 'a, 'de, 'src |
Conversion Method Prefixes
| Prefix | Cost | Ownership | Example |
|---|
as_ | Free | Borrowed → Borrowed | fn as_str(&self) -> &str |
to_ | Expensive | Borrowed → Owned | fn to_string(&self) -> String |
into_ | Variable | Owned → Owned | fn into_inner(self) -> T |
Getter/Setter Conventions
impl Foo {
fn bar(&self) -> &Bar { &self.bar }
fn bar_mut(&mut self) -> &mut Bar { &mut self.bar }
fn set_bar(&mut self, bar: Bar) { self.bar = bar; }
fn into_bar(self) -> Bar { self.bar }
}
Iterator Methods
impl MyCollection<T> {
fn iter(&self) -> impl Iterator<Item = &T>
fn iter_mut(&mut self) -> impl Iterator<Item = &mut T>
fn into_iter(self) -> impl Iterator<Item = T>
}
Error Design Quick Guide
| Context | Approach |
|---|
| Application binary | anyhow::Error for convenience |
| Library crate | Custom error enum with thiserror |
| Multiple error sources | Enum variants with #[from] |
| Performance critical | Boxed trait object: Box<dyn Error> |
| FFI boundary | Error codes with repr(C) enum |
Common Patterns
Builder Pattern:
let config = ConfigBuilder::new()
.name("my-app")
.timeout(Duration::from_secs(30))
.build()?;
Newtype Pattern:
struct Email(String);
impl Email {
pub fn new(s: impl Into<String>) -> Result<Self, InvalidEmail> { ... }
}
Type State Pattern:
struct Connection<State> { ... }
struct Disconnected;
struct Connected;
impl Connection<Disconnected> {
fn connect(self) -> Result<Connection<Connected>, Error> { ... }
}
impl Connection<Connected> {
fn send(&self, data: &[u8]) -> Result<(), Error> { ... }
}
Common Pitfalls to Avoid
- Integer overflow in release mode - Use
checked_*, saturating_*, or wrapping_*
- Unsafe
as casts - Use From/TryFrom for safe conversions
- Array indexing without bounds checking - Use
.get() or iterators
- Sensitive data in
Debug output - Implement Debug manually to redact
- TOCTOU races - Check and act atomically when possible
- Ignoring
#[must_use] warnings - Results must be handled
- Blocking in async contexts - Use
spawn_blocking for CPU-bound work
References
For detailed guidance on specific topics, see the reference files:
- Naming Conventions - RFC 430, API guidelines, method prefixes
- Error Handling - Error types, Result patterns, thiserror/anyhow
- Type Safety - Newtypes, builders, making invalid states unrepresentable
- Documentation - Doc comments, examples, rustdoc best practices
- Common Patterns - Iterators, pattern matching, module organization
- Pitfalls - Safe Rust gotchas and how to avoid them
- Resources - Curated links to articles, books, and talks
Checklist for Code Review
When reviewing Rust code, verify: