---
description: "Rust development: ownership, lifetimes, error handling, async patterns, and cargo workflows"
globs: ["**/*.rs", "**/Cargo.toml", "**/Cargo.lock"]
alwaysApply: false
---
# Rust Development Patterns
Idiomatic Rust patterns focusing on ownership, safety, and performance.
## CRITICAL: Agentic-First Rust Development
### Pre-Development Verification (MANDATORY)
Before writing ANY Rust code:
```
1. CHECK RUST INSTALLATION
→ run_terminal_cmd("rustc --version")
→ run_terminal_cmd("cargo --version")
2. VERIFY CURRENT VERSIONS (use web_search)
→ web_search("Rust stable version December 2024")
→ web_search("Rust edition 2024 features")
3. CHECK EXISTING PROJECT
→ Does Cargo.toml exist? Read it first!
→ What Rust edition is specified?
→ What dependencies are already present?
4. FOR NEW PROJECTS - USE cargo new
→ NEVER manually create Cargo.toml from scratch
→ run_terminal_cmd("cargo new my_project")
→ run_terminal_cmd("cargo new --lib my_library")
```
### CLI-First Rust Development
**ALWAYS use Cargo CLI:**
```bash
# Project creation (NEVER manually create Cargo.toml)
cargo new my_project
cargo new --lib my_library
cargo init # In existing directory
# Add dependencies (NEVER manually edit Cargo.toml for deps)
cargo add tokio --features full
cargo add serde --features derive
cargo add thiserror
cargo add anyhow
# Development dependencies
cargo add --dev tokio-test
cargo add --dev mockall
# Build and verify
cargo build
cargo check # Faster than build, just checks
cargo clippy # Linting
cargo fmt # Format code
# Test
cargo test
cargo test -- --nocapture # See println output
```
### Post-Edit Verification
After ANY Rust code changes, ALWAYS run:
```bash
# Check compilation
cargo check
# Lint for issues
cargo clippy -- -D warnings
# Run tests
cargo test
# Format code
cargo fmt --check
```
### Common Rust Syntax Traps (Avoid These!)
```rust
// WRONG: Using unwrap in production code
let value = some_option.unwrap(); // Panics on None!
let data = result.unwrap(); // Panics on Err!
// CORRECT: Handle errors properly
let value = some_option.ok_or(MyError::NotFound)?;
let data = result.map_err(|e| MyError::from(e))?;
// WRONG: Borrowing across await points
async fn bad_example(data: &mut Data) {
let reference = &data.field;
async_operation().await; // reference held across await!
use_reference(reference);
}
// CORRECT: Clone or restructure
async fn good_example(data: &mut Data) {
let value = data.field.clone();
async_operation().await;
use_value(value);
}
// WRONG: Missing Send bound for async traits
trait MyAsyncTrait {
async fn do_work(&self); // Won't compile in multi-threaded!
}
// CORRECT: Add Send bound when needed
trait MyAsyncTrait: Send + Sync {
fn do_work(&self) -> impl Future<Output = ()> + Send;
}
// WRONG: String vs &str confusion
fn greet(name: String) { } // Takes ownership unnecessarily
greet("hello".to_string()); // Wasteful allocation
// CORRECT: Accept borrowed when possible
fn greet(name: &str) { } // Borrows, no allocation needed
greet("hello"); // Works directly with string literal
```
### Cargo.toml Best Practices
```toml
[package]
name = "myproject"
version = "0.1.0"
edition = "2021" # Always specify edition
rust-version = "1.75" # Minimum Rust version
[dependencies]
# Pin to semver-compatible range
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
[dev-dependencies]
tokio-test = "0.4"
# Optimize release builds
[profile.release]
lto = true
codegen-units = 1
```
---
## Ownership & Borrowing
### Core Principles
1. Each value has exactly one owner
2. When the owner goes out of scope, the value is dropped
3. References must always be valid
4. Either one mutable reference OR any number of immutable references
### Borrowing Patterns
```rust
// Immutable borrow - read only
fn print_length(s: &str) {
println!("Length: {}", s.len());
}
// Mutable borrow - can modify
fn append_suffix(s: &mut String) {
s.push_str("_suffix");
}
// Taking ownership - consumes the value
fn consume_string(s: String) {
println!("Consumed: {}", s);
// s is dropped here
}
// Returning ownership
fn create_string() -> String {
String::from("created")
}
```
### When to Use What
- `&T`: Reading data, most function parameters
- `&mut T`: Modifying data in place
- `T`: Taking ownership, returning from functions, storing in structs
---
## Lifetimes
### Basic Lifetime Annotations
```rust
// The returned reference lives as long as the shortest input lifetime
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
// Struct with references
struct ImportantExcerpt<'a> {
part: &'a str,
}
impl<'a> ImportantExcerpt<'a> {
fn level(&self) -> i32 {
3
}
fn announce_and_return_part(&self, announcement: &str) -> &str {
println!("Attention: {}", announcement);
self.part
}
}
```
### Lifetime Elision Rules
The compiler infers lifetimes when:
1. Each reference parameter gets its own lifetime
2. If exactly one input lifetime, it's assigned to all outputs
3. If `&self` or `&mut self`, that lifetime is assigned to outputs
```rust
// These are equivalent:
fn first_word(s: &str) -> &str { ... }
fn first_word<'a>(s: &'a str) -> &'a str { ... }
```
---
## Error Handling
### Result and Option
```rust
use std::fs::File;
use std::io::{self, Read};
// Using Result
fn read_file(path: &str) -> Result<String, io::Error> {
let mut file = File::open(path)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
Ok(contents)
}
// Using Option
fn find_user(id: u64) -> Option<User> {
users.iter().find(|u| u.id == id).cloned()
}
// Combining with ?
fn get_user_email(id: u64) -> Option<String> {
let user = find_user(id)?;
Some(user.email)
}
```
### Custom Error Types
```rust
use thiserror::Error;
#[derive(Error, Debug)]
pub enum AppError {
#[error("Database error: {0}")]
Database(#[from] sqlx::Error),
#[error("Not found: {resource} with id {id}")]
NotFound { resource: String, id: u64 },
#[error("Validation error: {0}")]
Validation(String),
}
// Using anyhow for applications
use anyhow::{Context, Result};
fn load_config() -> Result<Config> {
let contents = std::fs::read_to_string("config.toml")
.context("Failed to read config file")?;
let config: Config = toml::from_str(&contents)
.context("Failed to parse config")?;
Ok(config)
}
```
### Error Handling Best Practices
- Use `thiserror` for library error types
- Use `anyhow` for application error handling
- Prefer `?` operator over `unwrap()` in production code
- Use `expect()` only when panic is truly unrecoverable
---
## Async Rust
### Async/Await Basics
```rust
use tokio;
#[tokio::main]
async fn main() {
let result = fetch_data("https://api.example.com").await;
println!("{:?}", result);
}
async fn fetch_data(url: &str) -> Result<String, reqwest::Error> {
let response = reqwest::get(url).await?;
let body = response.text().await?;
Ok(body)
}
```
### Concurrent Operations
```rust
use tokio::join;
use futures::future::join_all;
// Run multiple futures concurrently
async fn fetch_all() -> (Data1, Data2) {
let (data1, data2) = join!(
fetch_data1(),
fetch_data2()
);
(data1.unwrap(), data2.unwrap())
}
// Dynamic number of futures
async fn fetch_many(urls: Vec<String>) -> Vec<String> {
let futures: Vec<_> = urls.iter()
.map(|url| fetch_data(url))
.collect();
join_all(futures).await
.into_iter()
.filter_map(|r| r.ok())
.collect()
}
```
### Streams
```rust
use tokio_stream::StreamExt;
async fn process_stream() {
let mut stream = tokio_stream::iter(vec![1, 2, 3]);
while let Some(value) = stream.next().await {
println!("{}", value);
}
}
```
---
## Common Patterns
### Builder Pattern
```rust
#[derive(Default)]
pub struct RequestBuilder {
url: String,
method: Method,
headers: HashMap<String, String>,
body: Option<Vec<u8>>,
}
impl RequestBuilder {
pub fn new(url: impl Into<String>) -> Self {
Self {
url: url.into(),
..Default::default()
}
}
pub fn method(mut self, method: Method) -> Self {
self.method = method;
self
}
pub fn header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.headers.insert(key.into(), value.into());
self
}
pub fn body(mut self, body: Vec<u8>) -> Self {
self.body = Some(body);
self
}
pub fn build(self) -> Request {
Request {
url: self.url,
method: self.method,
headers: self.headers,
body: self.body,
}
}
}
// Usage
let request = RequestBuilder::new("https://api.example.com")
.method(Method::POST)
.header("Content-Type", "application/json")
.body(data)
.build();
```
### Newtype Pattern
```rust
// Type-safe wrappers
pub struct UserId(pub u64);
pub struct OrderId(pub u64);
// Can't accidentally pass OrderId where UserId expected
fn get_user(id: UserId) -> Option<User> { ... }
// Implement traits as needed
impl From<u64> for UserId {
fn from(id: u64) -> Self {
UserId(id)
}
}
```
### Type State Pattern
```rust
pub struct Request<State> {
inner: RequestInner,
_state: PhantomData<State>,
}
pub struct Draft;
pub struct Ready;
impl Request<Draft> {
pub fn new() -> Self { ... }
pub fn set_url(mut self, url: String) -> Self {
self.inner.url = Some(url);
self
}
pub fn finalize(self) -> Result<Request<Ready>, ValidationError> {
// Validate and transition state
Ok(Request { inner: self.inner, _state: PhantomData })
}
}
impl Request<Ready> {
pub async fn send(self) -> Result<Response, Error> {
// Only Ready requests can be sent
}
}
```
---
## Traits & Generics
### Trait Definitions
```rust
pub trait Repository<T> {
fn get(&self, id: u64) -> Option<T>;
fn save(&mut self, item: T) -> Result<(), Error>;
fn delete(&mut self, id: u64) -> Result<(), Error>;
}
// Default implementations
pub trait Greet {
fn name(&self) -> &str;
fn greet(&self) -> String {
format!("Hello, {}!", self.name())
}
}
```
### Trait Bounds
```rust
// Single bound
fn print_debug<T: Debug>(item: T) {
println!("{:?}", item);
}
// Multiple bounds
fn process<T: Clone + Send + Sync>(item: T) { ... }
// Where clauses for complex bounds
fn complex_function<T, U>(t: T, u: U) -> Result<(), Error>
where
T: Repository<User> + Clone,
U: Into<String> + Send,
{
// ...
}
// Impl trait for return types
fn create_iterator() -> impl Iterator<Item = i32> {
(0..10).filter(|x| x % 2 == 0)
}
```
---
## Cargo & Project Structure
### Cargo.toml
```toml
[package]
name = "myproject"
version = "0.1.0"
edition = "2021"
rust-version = "1.75"
[dependencies]
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
thiserror = "1"
anyhow = "1"
[dev-dependencies]
tokio-test = "0.4"
[features]
default = []
full = ["feature-a", "feature-b"]
feature-a = []
feature-b = ["dep:optional-dep"]
[profile.release]
lto = true
codegen-units = 1
```
### Project Layout
```
myproject/
src/
lib.rs # Library root
main.rs # Binary entry point
config.rs # Configuration
error.rs # Error types
models/ # Data structures
mod.rs
user.rs
services/ # Business logic
mod.rs
handlers/ # Request handlers
mod.rs
tests/ # Integration tests
integration_test.rs
benches/ # Benchmarks
benchmark.rs
examples/ # Example binaries
example.rs
```
### Module Organization
```rust
// src/lib.rs
pub mod config;
pub mod error;
pub mod models;
pub mod services;
pub use error::Error;
pub use config::Config;
// src/models/mod.rs
mod user;
mod order;
pub use user::User;
pub use order::Order;
```
---
## Testing
### Unit Tests
```rust
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add() {
assert_eq!(add(2, 2), 4);
}
#[test]
#[should_panic(expected = "division by zero")]
fn test_divide_by_zero() {
divide(1, 0);
}
#[test]
fn test_result() -> Result<(), Error> {
let result = fallible_function()?;
assert_eq!(result, expected);
Ok(())
}
}
```
### Async Tests
```rust
#[tokio::test]
async fn test_async_function() {
let result = async_function().await;
assert!(result.is_ok());
}
```
### Integration Tests
```rust
// tests/integration_test.rs
use myproject::Config;
#[test]
fn test_config_loading() {
let config = Config::load("test_config.toml").unwrap();
assert_eq!(config.port, 8080);
}
```
---
## Performance Tips
### Avoid Unnecessary Allocations
```rust
// Bad: creates new String each iteration
for item in items {
let key = format!("key_{}", item.id);
}
// Good: reuse buffer
let mut key = String::with_capacity(20);
for item in items {
key.clear();
write!(&mut key, "key_{}", item.id).unwrap();
}
```
### Use Iterators
```rust
// Bad: intermediate collections
let filtered: Vec<_> = items.iter().filter(|x| x.active).collect();
let mapped: Vec<_> = filtered.iter().map(|x| x.value).collect();
let sum: i32 = mapped.iter().sum();
// Good: lazy iterator chain
let sum: i32 = items.iter()
.filter(|x| x.active)
.map(|x| x.value)
.sum();
```
### Smart Pointers
```rust
// Rc for shared ownership (single-threaded)
use std::rc::Rc;
let shared = Rc::new(data);
// Arc for shared ownership (multi-threaded)
use std::sync::Arc;
let shared = Arc::new(data);
// Box for heap allocation
let boxed: Box<dyn Trait> = Box::new(implementation);
// Cow for clone-on-write
use std::borrow::Cow;
fn process(input: Cow<str>) -> Cow<str> {
if needs_modification(&input) {
Cow::Owned(modify(input.into_owned()))
} else {
input
}
}
```