name: rust-expert description: Expert in Rust development including ownership, lifetimes, traits, async Rust, error handling, and systems programming. Covers tokio, serde, and common ecosystem patterns.
You are a Rust expert specializing in ownership, lifetimes, traits, async programming, and high-performance systems code. This document provides comprehensive patterns for modern Rust development.
// 1. Each value has exactly one owner
// 2. When owner goes out of scope, value is dropped
// 3. Ownership can be transferred (moved) or borrowed
fn main() {
let s1 = String::from("hello");
let s2 = s1; // s1 moved to s2, s1 no longer valid
// println!("{}", s1); // ERROR: value moved
let s3 = s2.clone(); // Deep copy, both valid
println!("{} {}", s2, s3);
}
fn main() {
let s = String::from("hello");
// Immutable borrow (multiple allowed)
let len = calculate_length(&s);
println!("Length of '{}' is {}", s, len);
// Mutable borrow (only one allowed)
let mut s = String::from("hello");
change(&mut s);
}
fn calculate_length(s: &str) -> usize {
s.len()
}
fn change(s: &mut String) {
s.push_str(", world");
}
// 1. Multiple immutable borrows OR one mutable borrow
// 2. References must always be valid
fn main() {
let mut s = String::from("hello");
let r1 = &s; // OK
let r2 = &s; // OK - multiple immutable
// let r3 = &mut s; // ERROR: can't borrow as mutable
println!("{} {}", r1, r2);
// r1, r2 no longer used after this point
let r3 = &mut s; // OK - previous borrows ended
r3.push_str("!");
}
// Lifetime tells compiler how long references are valid
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
// Multiple lifetimes
fn first_word<'a, 'b>(s: &'a str, _other: &'b str) -> &'a str {
s.split_whitespace().next().unwrap_or("")
}
struct Excerpt<'a> {
part: &'a str,
}
impl<'a> Excerpt<'a> {
fn level(&self) -> i32 {
3
}
fn announce_and_return(&self, announcement: &str) -> &str {
println!("Attention: {}", announcement);
self.part
}
}
// These are equivalent due to elision rules:
fn first_word(s: &str) -> &str { ... }
fn first_word<'a>(s: &'a str) -> &'a str { ... }
// Rules:
// 1. Each reference parameter gets its own lifetime
// 2. If one input lifetime, output gets same lifetime
// 3. If &self or &mut self, output gets self's lifetime
// 'static means reference lives for entire program
let s: &'static str = "I have a static lifetime.";
// Common in error types
fn make_error() -> Box<dyn std::error::Error + 'static> {
Box::new(std::io::Error::new(std::io::ErrorKind::Other, "error"))
}
pub trait Summary {
fn summarize(&self) -> String;
// Default implementation
fn summarize_author(&self) -> String {
String::from("(anonymous)")
}
}
pub struct Article {
pub headline: String,
pub content: String,
}
impl Summary for Article {
fn summarize(&self) -> String {
format!("{}", self.headline)
}
}
// Trait bound syntax
fn notify<T: Summary>(item: &T) {
println!("Breaking news! {}", item.summarize());
}
// Multiple bounds
fn notify<T: Summary + Display>(item: &T) { ... }
// Where clause (cleaner for complex bounds)
fn some_function<T, U>(t: &T, u: &U) -> i32
where
T: Display + Clone,
U: Clone + Debug,
{
// ...
}
// impl Trait (simpler return types)
fn returns_summarizable() -> impl Summary {
Article { headline: "...", content: "..." }
}
// Clone - explicit duplication
#[derive(Clone)]
struct Point { x: i32, y: i32 }
// Copy - implicit copy on assignment (requires Clone)
#[derive(Clone, Copy)]
struct Point { x: i32, y: i32 }
// Debug - {:?} formatting
#[derive(Debug)]
struct Point { x: i32, y: i32 }
// Display - {} formatting
impl std::fmt::Display for Point {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "({}, {})", self.x, self.y)
}
}
// Default - default values
#[derive(Default)]
struct Config {
debug: bool, // false
timeout: u64, // 0
name: String, // ""
}
// PartialEq, Eq - equality comparison
#[derive(PartialEq, Eq)]
struct Point { x: i32, y: i32 }
// PartialOrd, Ord - ordering comparison
#[derive(PartialOrd, Ord, PartialEq, Eq)]
struct Point { x: i32, y: i32 }
// Hash - for HashMap/HashSet keys
#[derive(Hash, PartialEq, Eq)]
struct Point { x: i32, y: i32 }
struct Wrapper(String);
impl From<String> for Wrapper {
fn from(s: String) -> Self {
Wrapper(s)
}
}
impl From<&str> for Wrapper {
fn from(s: &str) -> Self {
Wrapper(s.to_string())
}
}
// Usage (Into comes free with From)
let w: Wrapper = "hello".into();
let w = Wrapper::from("hello");
// Result<T, E> for recoverable errors
fn read_file(path: &str) -> Result<String, std::io::Error> {
std::fs::read_to_string(path)
}
// Option<T> for optional values
fn find_user(id: u64) -> Option<User> {
users.get(&id).cloned()
}
// ? operator for propagation
fn process_file(path: &str) -> Result<Data, Box<dyn Error>> {
let content = std::fs::read_to_string(path)?;
let data: Data = serde_json::from_str(&content)?;
Ok(data)
}
use thiserror::Error;
#[derive(Error, Debug)]
pub enum AppError {
#[error("Database error: {0}")]
Database(#[from] sqlx::Error),
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("Not found: {0}")]
NotFound(String),
#[error("Validation error: {field} - {message}")]
Validation { field: String, message: String },
}
// Usage
fn get_user(id: u64) -> Result<User, AppError> {
let user = db.find(id)
.ok_or_else(|| AppError::NotFound(format!("User {}", id)))?;
Ok(user)
}
use anyhow::{Context, Result, bail, ensure};
fn process() -> Result<()> {
let config = load_config()
.context("Failed to load configuration")?;
ensure!(config.valid, "Configuration is invalid");
if config.debug {
bail!("Debug mode not allowed in production");
}
Ok(())
}
// Anyhow is for applications
// thiserror is for libraries
// Match on specific errors
match result {
Ok(value) => println!("{}", value),
Err(AppError::NotFound(msg)) => println!("Not found: {}", msg),
Err(e) => return Err(e),
}
// Convert Option to Result
let user = find_user(id).ok_or(AppError::NotFound("user"))?;
// Map errors
let result = operation().map_err(AppError::from)?;
// Combine Results
let (a, b) = (get_a()?, get_b()?);
// Collect Results
let values: Result<Vec<_>, _> = items.iter().map(process).collect();
// Async function
async fn fetch_url(url: &str) -> Result<String> {
let response = reqwest::get(url).await?;
let body = response.text().await?;
Ok(body)
}
// Running async code
#[tokio::main]
async fn main() {
let result = fetch_url("https://example.com").await;
println!("{:?}", result);
}
use tokio::time::{sleep, Duration};
#[tokio::main]
async fn main() {
// Spawn concurrent tasks
let handle1 = tokio::spawn(async {
sleep(Duration::from_secs(1)).await;
"Task 1 done"
});
let handle2 = tokio::spawn(async {
sleep(Duration::from_secs(2)).await;
"Task 2 done"
});
// Wait for both
let (r1, r2) = tokio::join!(handle1, handle2);
println!("{:?} {:?}", r1, r2);
}
use tokio::select;
async fn race_operations() -> Result<Data> {
select! {
result = operation_a() => {
println!("A finished first");
result
}
result = operation_b() => {
println!("B finished first");
result
}
_ = tokio::time::sleep(Duration::from_secs(5)) => {
Err(anyhow!("Timeout"))
}
}
}
use tokio::sync::mpsc;
#[tokio::main]
async fn main() {
let (tx, mut rx) = mpsc::channel(100);
// Spawn sender
tokio::spawn(async move {
for i in 0..10 {
tx.send(i).await.unwrap();
}
});
// Receive
while let Some(value) = rx.recv().await {
println!("Received: {}", value);
}
}
use async_trait::async_trait;
#[async_trait]
pub trait DataStore {
async fn get(&self, key: &str) -> Option<String>;
async fn set(&self, key: &str, value: String) -> Result<()>;
}
#[async_trait]
impl DataStore for RedisStore {
async fn get(&self, key: &str) -> Option<String> {
self.client.get(key).await.ok()
}
async fn set(&self, key: &str, value: String) -> Result<()> {
self.client.set(key, value).await?;
Ok(())
}
}
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
pub struct User {
pub id: u64,
pub name: String,
#[serde(default)]
pub active: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub email: Option<String>,
}
// JSON
let json = serde_json::to_string(&user)?;
let user: User = serde_json::from_str(&json)?;
// TOML
let toml = toml::to_string(&config)?;
let config: Config = toml::from_str(&toml)?;
#[derive(Serialize, Deserialize)]
pub struct Config {
#[serde(rename = "serverPort")]
pub server_port: u16,
#[serde(default = "default_timeout")]
pub timeout: u64,
#[serde(skip)]
pub internal: InternalState,
#[serde(flatten)]
pub extra: HashMap<String, Value>,
#[serde(with = "chrono::serde::ts_seconds")]
pub timestamp: DateTime<Utc>,
}
fn default_timeout() -> u64 { 30 }
use serde::{Serializer, Deserializer};
#[derive(Debug)]
pub struct Url(String);
impl Serialize for Url {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.0)
}
}
impl<'de> Deserialize<'de> for Url {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
if s.starts_with("http") {
Ok(Url(s))
} else {
Err(serde::de::Error::custom("Invalid URL"))
}
}
}
use std::collections::{HashMap, HashSet, VecDeque, BTreeMap};
// Vec
let mut vec = vec![1, 2, 3];
vec.push(4);
vec.extend([5, 6, 7]);
// HashMap
let mut map = HashMap::new();
map.insert("key", "value");
map.entry("key").or_insert("default");
// HashSet
let mut set = HashSet::new();
set.insert(1);
set.contains(&1);
// VecDeque (double-ended queue)
let mut deque = VecDeque::new();
deque.push_back(1);
deque.push_front(0);
let numbers = vec![1, 2, 3, 4, 5];
// Map and collect
let doubled: Vec<_> = numbers.iter().map(|x| x * 2).collect();
// Filter
let evens: Vec<_> = numbers.iter().filter(|x| *x % 2 == 0).collect();
// Fold/reduce
let sum: i32 = numbers.iter().sum();
let product: i32 = numbers.iter().product();
let custom = numbers.iter().fold(0, |acc, x| acc + x);
// Find
let first_even = numbers.iter().find(|x| *x % 2 == 0);
// Any/All
let has_even = numbers.iter().any(|x| x % 2 == 0);
let all_positive = numbers.iter().all(|x| *x > 0);
// Chain
let combined: Vec<_> = vec1.iter().chain(vec2.iter()).collect();
// Flatten
let nested = vec![vec![1, 2], vec![3, 4]];
let flat: Vec<_> = nested.into_iter().flatten().collect();
// Zip
let pairs: Vec<_> = names.iter().zip(ages.iter()).collect();
// Enumerate
for (i, item) in items.iter().enumerate() {
println!("{}: {}", i, item);
}
struct Counter {
count: usize,
max: usize,
}
impl Iterator for Counter {
type Item = usize;
fn next(&mut self) -> Option<Self::Item> {
if self.count < self.max {
self.count += 1;
Some(self.count)
} else {
None
}
}
}
impl Counter {
fn new(max: usize) -> Self {
Counter { count: 0, max }
}
}
// Box - heap allocation, single owner
let boxed = Box::new(5);
let large_data = Box::new([0u8; 1_000_000]);
// Rc - reference counting (single-threaded)
use std::rc::Rc;
let data = Rc::new(vec![1, 2, 3]);
let clone1 = Rc::clone(&data);
let clone2 = Rc::clone(&data);
// Arc - atomic reference counting (thread-safe)
use std::sync::Arc;
let data = Arc::new(vec![1, 2, 3]);
let clone = Arc::clone(&data);
std::thread::spawn(move || {
println!("{:?}", clone);
});
// RefCell - interior mutability (single-threaded)
use std::cell::RefCell;
let data = RefCell::new(5);
*data.borrow_mut() += 1;
// Mutex - interior mutability (thread-safe)
use std::sync::Mutex;
let data = Arc::new(Mutex::new(vec![]));
let clone = Arc::clone(&data);
std::thread::spawn(move || {
let mut lock = clone.lock().unwrap();
lock.push(1);
});
// RwLock - multiple readers OR single writer
use std::sync::RwLock;
let data = RwLock::new(vec![1, 2, 3]);
let read = data.read().unwrap(); // Multiple readers OK
let mut write = data.write().unwrap(); // Exclusive write
use std::borrow::Cow;
fn process(input: &str) -> Cow<str> {
if input.contains(' ') {
Cow::Owned(input.replace(' ', "_"))
} else {
Cow::Borrowed(input)
}
}
// Avoids allocation when not needed
let result = process("hello"); // Borrowed, no allocation
let result = process("hello world"); // Owned, allocates
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add() {
assert_eq!(add(2, 3), 5);
}
#[test]
#[should_panic(expected = "division by zero")]
fn test_divide_by_zero() {
divide(1, 0);
}
#[test]
fn test_result() -> Result<(), String> {
let result = parse("42")?;
assert_eq!(result, 42);
Ok(())
}
}
#[tokio::test]
async fn test_async_function() {
let result = fetch_data().await;
assert!(result.is_ok());
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_concurrent() {
// Uses multi-threaded runtime
}
// tests/integration_test.rs
use mylib::public_function;
#[test]
fn test_public_api() {
let result = public_function();
assert!(result.is_ok());
}
/// Adds two numbers.
///
/// # Examples
///
/// ```
/// use mylib::add;
/// assert_eq!(add(2, 3), 5);
/// ```
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
[package]
name = "myapp"
version = "0.1.0"
edition = "2021"
authors = ["Your Name <you@example.com>"]
description = "My Application"
license = "MIT"
[dependencies]
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
thiserror = "1"
anyhow = "1"
tracing = "0.1"
tracing-subscriber = "0.3"
[dev-dependencies]
mockall = "0.11"
tempfile = "3"
[features]
default = []
full = ["feature-a", "feature-b"]
feature-a = []
feature-b = ["dep:optional-dep"]
[profile.release]
lto = true
codegen-units = 1
src/
├── main.rs
├── lib.rs
├── config.rs
├── error.rs
├── models/
│ ├── mod.rs
│ └── user.rs
├── services/
│ ├── mod.rs
│ └── user_service.rs
└── handlers/
├── mod.rs
└── user_handler.rs
// src/lib.rs
pub mod config;
pub mod error;
pub mod models;
pub mod services;
pub mod handlers;
pub use error::Error;