| name | code:rust |
| description | Rust coding practices and idioms. Error handling (thiserror/anyhow), newtypes, iterators, builders, modules.
<example>
Context: User is writing Rust code
user: "implement a parser for this config format"
</example>
<example>
Context: User needs Rust patterns
user: "what's the best way to handle errors in this library"
</example>
|
Tools Reference
Built-in Tools
| Tool | Purpose |
|---|
Read | Read .rs files |
Write | Create new Rust files |
Edit | Modify Rust code |
Bash | Run cargo, rustc, clippy, rustfmt |
Glob | Find Rust files (*.rs) |
Grep | Search Rust code |
Related Skills
marauder:code:rust-cli - Clap CLI development
marauder:code:rust-test - Rust testing
marauder:code:rust-dioxus - Dioxus GUI development
marauder:code:rust-tooling - Lint/format/typecheck/validate
Rust Coding Practices
Modern Rust idioms focused on safety, performance, and clarity.
Parse, Don't Validate
Make invalid states unrepresentable via newtype wrappers:
pub struct Email(String);
impl Email {
pub fn parse(s: &str) -> Result<Self, EmailError> {
if s.contains('@') && s.contains('.') {
Ok(Self(s.to_owned()))
} else {
Err(EmailError::InvalidFormat)
}
}
pub fn as_str(&self) -> &str {
&self.0
}
}
fn send_email(to: Email, subject: Subject) { ... }
Error Handling
Library Code: thiserror
use thiserror::Error;
#[derive(Debug, Error)]
pub enum ParseError {
#[error("invalid format: {0}")]
InvalidFormat(String),
#[error("missing field: {field}")]
MissingField { field: &'static str },
#[error(transparent)]
Io(#[from] std::io::Error),
}
Application Code: anyhow
use anyhow::{Context, Result};
fn load_config(path: &Path) -> Result<Config> {
let contents = fs::read_to_string(path)
.context("failed to read config file")?;
let config: Config = toml::from_str(&contents)
.context("failed to parse config")?;
Ok(config)
}
Builder Pattern
#[derive(Default)]
pub struct RequestBuilder {
url: Option<String>,
method: Method,
headers: HashMap<String, String>,
}
impl RequestBuilder {
pub fn new() -> Self { Self::default() }
pub fn url(mut self, url: impl Into<String>) -> Self {
self.url = Some(url.into());
self
}
pub fn build(self) -> Result<Request, BuildError> {
let url = self.url.ok_or(BuildError::MissingUrl)?;
Ok(Request { url, method: self.method, headers: self.headers })
}
}
Iterators Over Loops
let results: Vec<_> = items
.iter()
.filter(|item| item.is_valid())
.map(|item| item.process())
.collect();
let results: Result<Vec<_>, _> = items
.iter()
.map(|item| process(item))
.collect();
Smart Pointers
let data: Box<LargeStruct> = Box::new(large_struct);
let shared: Arc<Config> = Arc::new(config);
let mutex: Mutex<Vec<i32>> = Mutex::new(vec![]);
Module Organization
pub mod config;
mod internal;
pub use config::Config;
Prelude Pattern
pub use crate::config::Config;
pub use crate::error::{Error, Result};
Flatten API Exports
mod client;
pub use client::DockerClient;
.unwrap() Policy
Avoid in library code. Use instead:
.unwrap_or(default)
.unwrap_or_default()
.expect("reason this can't fail")
.ok_or(Error::Missing)?
Acceptable:
- Tests
- After infallible operations:
Regex::new(r"^\d+$").unwrap()
Forbidden
Never commit:
dbg!() - debug macro
todo!() - unfinished code
panic!() for recoverable errors
.unwrap() on user input or external data
use foo::* (except use super::* in tests)