|
| 1 | +--- |
| 2 | +name: rust |
| 3 | +description: Rust coding best practices for idiomatic, efficient, and maintainable code. Use when writing Rust code, reviewing code, or learning Rust patterns. |
| 4 | +allowed-tools: Read, Edit, Write, Bash, Grep, Glob, Task |
| 5 | +--- |
| 6 | + |
| 7 | +# Rust Best Practices |
| 8 | + |
| 9 | +Guidelines for writing idiomatic, efficient, and maintainable Rust code. |
| 10 | + |
| 11 | +## Core Principles |
| 12 | + |
| 13 | +1. **Leverage the type system** - Make invalid states unrepresentable |
| 14 | +2. **Prefer compile-time checks** - Catch errors before runtime |
| 15 | +3. **Be explicit about ownership** - Don't fight the borrow checker |
| 16 | +4. **Write code that passes fmt/clippy first** - Not after fixing |
| 17 | + |
| 18 | +## Code health |
| 19 | + |
| 20 | +1. Prefer smaller files, refactor large files into multi-file modules |
| 21 | +2. Refactor large modules into several |
| 22 | +3. Prefer re-usable functions. Don't create separate functions if re-use is not planned. |
| 23 | + |
| 24 | +## Error Handling |
| 25 | + |
| 26 | +Use `thiserror` |
| 27 | + |
| 28 | +```rust |
| 29 | +use thiserror::Error; |
| 30 | + |
| 31 | +#[derive(Error, Debug)] |
| 32 | +pub enum ConfigError { |
| 33 | + #[error("Failed to read config: {0}")] |
| 34 | + Io(#[from] std::io::Error), |
| 35 | + |
| 36 | + #[error("Failed to parse config: {0}")] |
| 37 | + Parse(#[from] toml::de::Error), |
| 38 | + |
| 39 | + #[error("Invalid configuration: {message}")] |
| 40 | + Invalid { message: String }, |
| 41 | +} |
| 42 | +``` |
| 43 | + |
| 44 | +### Never Use `.unwrap()` |
| 45 | + |
| 46 | +```rust |
| 47 | +// BAD |
| 48 | +let value = map.get("key").unwrap(); |
| 49 | + |
| 50 | +// GOOD |
| 51 | +let value = map.get("key").ok_or_else(|| Error::MissingKey("key"))?; |
| 52 | + |
| 53 | +// GOOD (when None is truly impossible) |
| 54 | +let value = map.get("key").expect("key always present after init"); |
| 55 | +``` |
| 56 | + |
| 57 | +## Ownership & Borrowing |
| 58 | + |
| 59 | +### Prefer Borrowing Over Cloning |
| 60 | + |
| 61 | +```rust |
| 62 | +// BAD - unnecessary clone |
| 63 | +fn process(data: String) { ... } |
| 64 | +process(my_string.clone()); |
| 65 | + |
| 66 | +// GOOD - borrow when possible |
| 67 | +fn process(data: &str) { ... } |
| 68 | +process(&my_string); |
| 69 | +``` |
| 70 | + |
| 71 | +### Use `Cow` for Flexible Ownership |
| 72 | + |
| 73 | +```rust |
| 74 | +use std::borrow::Cow; |
| 75 | + |
| 76 | +fn process(data: Cow<'_, str>) -> Cow<'_, str> { |
| 77 | + if data.contains("bad") { |
| 78 | + Cow::Owned(data.replace("bad", "good")) |
| 79 | + } else { |
| 80 | + data // No allocation if unchanged |
| 81 | + } |
| 82 | +} |
| 83 | +``` |
| 84 | + |
| 85 | +### Return Owned Data from Constructors |
| 86 | + |
| 87 | +```rust |
| 88 | +// GOOD - clear ownership |
| 89 | +impl User { |
| 90 | + pub fn new(name: impl Into<String>) -> Self { |
| 91 | + Self { name: name.into() } |
| 92 | + } |
| 93 | +} |
| 94 | +``` |
| 95 | + |
| 96 | +## API Design |
| 97 | + |
| 98 | +### Builder Pattern for Complex Configuration |
| 99 | + |
| 100 | +```rust |
| 101 | +#[derive(Default)] |
| 102 | +pub struct ServerBuilder { |
| 103 | + host: Option<String>, |
| 104 | + port: Option<u16>, |
| 105 | + timeout: Option<Duration>, |
| 106 | +} |
| 107 | + |
| 108 | +impl ServerBuilder { |
| 109 | + pub fn host(mut self, host: impl Into<String>) -> Self { |
| 110 | + self.host = Some(host.into()); |
| 111 | + self |
| 112 | + } |
| 113 | + |
| 114 | + pub fn port(mut self, port: u16) -> Self { |
| 115 | + self.port = Some(port); |
| 116 | + self |
| 117 | + } |
| 118 | + |
| 119 | + pub fn build(self) -> Result<Server, ConfigError> { |
| 120 | + Ok(Server { |
| 121 | + host: self.host.unwrap_or_else(|| "localhost".into()), |
| 122 | + port: self.port.ok_or(ConfigError::MissingPort)?, |
| 123 | + timeout: self.timeout.unwrap_or(Duration::from_secs(30)), |
| 124 | + }) |
| 125 | + } |
| 126 | +} |
| 127 | +``` |
| 128 | + |
| 129 | +### Newtype Pattern for Type Safety |
| 130 | + |
| 131 | +```rust |
| 132 | +// BAD - easy to mix up |
| 133 | +fn transfer(from: i64, to: i64, amount: i64) { ... } |
| 134 | + |
| 135 | +// GOOD - compile-time safety |
| 136 | +pub struct AccountId(i64); |
| 137 | +pub struct Amount(i64); |
| 138 | + |
| 139 | +fn transfer(from: AccountId, to: AccountId, amount: Amount) { ... } |
| 140 | +``` |
| 141 | + |
| 142 | +### Use `#[must_use]` for Important Returns |
| 143 | + |
| 144 | +```rust |
| 145 | +#[must_use] |
| 146 | +pub fn validate(&self) -> Result<(), ValidationError> { |
| 147 | + // ... |
| 148 | +} |
| 149 | +``` |
| 150 | + |
| 151 | +## Collections & Iterators |
| 152 | + |
| 153 | +### Prefer Iterators Over Loops |
| 154 | + |
| 155 | +```rust |
| 156 | +// BAD |
| 157 | +let mut results = Vec::new(); |
| 158 | +for item in items { |
| 159 | + if item.is_valid() { |
| 160 | + results.push(item.transform()); |
| 161 | + } |
| 162 | +} |
| 163 | + |
| 164 | +// GOOD |
| 165 | +let results: Vec<_> = items |
| 166 | + .into_iter() |
| 167 | + .filter(|item| item.is_valid()) |
| 168 | + .map(|item| item.transform()) |
| 169 | + .collect(); |
| 170 | +``` |
| 171 | + |
| 172 | +### Use `collect()` Type Inference |
| 173 | + |
| 174 | +```rust |
| 175 | +// Collect into Vec |
| 176 | +let vec: Vec<_> = iter.collect(); |
| 177 | + |
| 178 | +// Collect into HashMap |
| 179 | +let map: HashMap<_, _> = iter.collect(); |
| 180 | + |
| 181 | +// Collect Results |
| 182 | +let results: Result<Vec<_>, _> = iter.collect(); |
| 183 | +``` |
| 184 | + |
| 185 | +## Async Patterns |
| 186 | + |
| 187 | +### Use `tokio` for Async Runtime |
| 188 | + |
| 189 | +```rust |
| 190 | +#[tokio::main] |
| 191 | +async fn main() -> Result<()> { |
| 192 | + let result = fetch_data().await?; |
| 193 | + Ok(()) |
| 194 | +} |
| 195 | +``` |
| 196 | + |
| 197 | +### Avoid Blocking in Async Code |
| 198 | + |
| 199 | +```rust |
| 200 | +// BAD - blocks the runtime |
| 201 | +async fn bad() { |
| 202 | + std::thread::sleep(Duration::from_secs(1)); |
| 203 | +} |
| 204 | + |
| 205 | +// GOOD - async sleep |
| 206 | +async fn good() { |
| 207 | + tokio::time::sleep(Duration::from_secs(1)).await; |
| 208 | +} |
| 209 | + |
| 210 | +// GOOD - spawn blocking for CPU-intensive work |
| 211 | +async fn compute() -> i32 { |
| 212 | + tokio::task::spawn_blocking(|| expensive_computation()).await.unwrap() |
| 213 | +} |
| 214 | +``` |
| 215 | + |
| 216 | +## Testing |
| 217 | + |
| 218 | +### Unit Tests in Same File |
| 219 | + |
| 220 | +```rust |
| 221 | +#[cfg(test)] |
| 222 | +mod tests { |
| 223 | + use super::*; |
| 224 | + |
| 225 | + #[test] |
| 226 | + fn test_basic() { |
| 227 | + assert_eq!(add(1, 2), 3); |
| 228 | + } |
| 229 | + |
| 230 | + #[test] |
| 231 | + fn test_edge_case() { |
| 232 | + assert!(validate("").is_err()); |
| 233 | + } |
| 234 | +} |
| 235 | +``` |
| 236 | + |
| 237 | +### Integration Tests in `tests/` |
| 238 | + |
| 239 | +```rust |
| 240 | +// tests/integration_test.rs |
| 241 | +use my_crate::public_api; |
| 242 | + |
| 243 | +#[test] |
| 244 | +fn test_full_workflow() { |
| 245 | + let result = public_api::process("input"); |
| 246 | + assert!(result.is_ok()); |
| 247 | +} |
| 248 | +``` |
| 249 | + |
| 250 | +### Use `assert!` Macros Effectively |
| 251 | + |
| 252 | +```rust |
| 253 | +assert!(condition); |
| 254 | +assert_eq!(left, right); |
| 255 | +assert_ne!(left, right); |
| 256 | +assert!(result.is_ok()); |
| 257 | +assert!(result.is_err()); |
| 258 | +assert_matches!(value, Pattern::Variant { .. }); |
| 259 | +``` |
| 260 | + |
| 261 | +## Performance |
| 262 | + |
| 263 | +### Avoid Premature Allocation |
| 264 | + |
| 265 | +```rust |
| 266 | +// BAD - allocates even if not needed |
| 267 | +fn maybe_string() -> String { |
| 268 | + String::from("default") |
| 269 | +} |
| 270 | + |
| 271 | +// GOOD - return static str when possible |
| 272 | +fn maybe_string() -> &'static str { |
| 273 | + "default" |
| 274 | +} |
| 275 | +``` |
| 276 | + |
| 277 | +### Use `Vec::with_capacity` for Known Sizes |
| 278 | + |
| 279 | +```rust |
| 280 | +// BAD - multiple reallocations |
| 281 | +let mut vec = Vec::new(); |
| 282 | +for i in 0..1000 { |
| 283 | + vec.push(i); |
| 284 | +} |
| 285 | + |
| 286 | +// GOOD - single allocation |
| 287 | +let mut vec = Vec::with_capacity(1000); |
| 288 | +for i in 0..1000 { |
| 289 | + vec.push(i); |
| 290 | +} |
| 291 | +``` |
| 292 | + |
| 293 | +### Profile Before Optimizing |
| 294 | + |
| 295 | +```bash |
| 296 | +cargo build --release |
| 297 | +cargo flamegraph # requires cargo-flamegraph |
| 298 | +``` |
| 299 | + |
| 300 | +## Module Organization |
| 301 | + |
| 302 | +### Keep Modules Focused |
| 303 | + |
| 304 | +```rust |
| 305 | +// src/lib.rs |
| 306 | +pub mod config; |
| 307 | +pub mod client; |
| 308 | +pub mod error; |
| 309 | + |
| 310 | +// Re-export public API |
| 311 | +pub use config::Config; |
| 312 | +pub use client::Client; |
| 313 | +pub use error::Error; |
| 314 | +``` |
| 315 | + |
| 316 | +### Use `pub(crate)` for Internal APIs |
| 317 | + |
| 318 | +```rust |
| 319 | +// Public to crate, not external users |
| 320 | +pub(crate) fn internal_helper() { ... } |
| 321 | +``` |
| 322 | + |
| 323 | +## Documentation |
| 324 | + |
| 325 | +### Document Public APIs |
| 326 | + |
| 327 | +```rust |
| 328 | +/// Creates a new client with the given configuration. |
| 329 | +/// |
| 330 | +/// # Arguments |
| 331 | +/// |
| 332 | +/// * `config` - The client configuration |
| 333 | +/// |
| 334 | +/// # Errors |
| 335 | +/// |
| 336 | +/// Returns an error if the configuration is invalid. |
| 337 | +/// |
| 338 | +/// # Examples |
| 339 | +/// |
| 340 | +/// ``` |
| 341 | +/// let client = Client::new(Config::default())?; |
| 342 | +/// ``` |
| 343 | +pub fn new(config: Config) -> Result<Self> { |
| 344 | + // ... |
| 345 | +} |
| 346 | +``` |
| 347 | + |
| 348 | +## Anti-Patterns to Avoid |
| 349 | + |
| 350 | +| Anti-Pattern | Better Approach | |
| 351 | +|--------------|-----------------| |
| 352 | +| `.unwrap()` everywhere | Use `?` operator | |
| 353 | +| `clone()` to satisfy borrow checker | Restructure ownership | |
| 354 | +| `String` parameters | Use `&str` or `impl Into<String>` | |
| 355 | +| Boolean parameters | Use enums | |
| 356 | +| Long function bodies | Extract to smaller functions | |
| 357 | +| Deep nesting | Use early returns | |
| 358 | +| Magic numbers | Use named constants | |
| 359 | + |
| 360 | +## Quick Reference |
| 361 | + |
| 362 | +```bash |
| 363 | +# Quality gates |
| 364 | +cargo fmt -- --check && cargo clippy -- -D warnings && cargo test |
| 365 | + |
| 366 | +# Common cargo commands |
| 367 | +cargo check # Fast syntax/type check |
| 368 | +cargo build # Debug build |
| 369 | +cargo build --release # Release build |
| 370 | +cargo nextest run # Run tests |
| 371 | +cargo doc --open # Generate and view docs |
| 372 | +cargo clippy --fix # Auto-fix lint issues |
| 373 | +``` |
0 commit comments