# Rust Programming Expert

> Expert skill for Rust programming (Rust 2024 / v1.85+). Use whenever the user writes, reviews, or debugs Rust code — covering memory safety (ownership, lifetimes), async (Tokio, async closures), web backends (Axum, SQLx), CLI tools (Clap, Serde), unsafe code, performance optimization, and Cargo profiling. Trigger on any mention of Rust, Cargo, Tokio, Axum, SQLx, Clap, Serde, lifetimes, ownership, or Rust error handling. Also trigger for systems programming, WebAssembly, or high-performance service development in Rust.

- Skill: `roedyrustam/rust-programming-expert-2` (Agent Skill)
- Install (CLI): `npx skillmds@latest add roedyrustam/rust-programming-expert-2`
- Raw SKILL.md: https://api.skillmd.com/api/skills/roedyrustam/rust-programming-expert-2/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: roedyrustam (https://skillmd.com/u/roedyrustam)
- Updated: 2026-09-21
- Page: https://skillmd.com/skills/roedyrustam/rust-programming-expert-2

---


# Rust Programming Expert (2024 Edition / v1.85+)

Production Rust patterns: memory safety, async-first, web backends, CLI, and performance.

---

## Project Setup

```bash
# New project
cargo new my-app --bin
cargo new my-lib --lib

# Essential Cargo.toml
[package]
name = "my-app"
version = "0.1.0"
edition = "2024"

[dependencies]
tokio = { version = "1", features = ["full"] }
axum = "0.8"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
sqlx = { version = "0.8", features = ["postgres", "runtime-tokio", "uuid", "chrono"] }
anyhow = "1"
thiserror = "2"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
uuid = { version = "1", features = ["v4", "serde"] }
chrono = { version = "0.4", features = ["serde"] }

[dev-dependencies]
tokio-test = "0.4"
```

---

## Ownership & Borrowing Patterns

### Common Ownership Patterns
```rust
// Clone when you need owned data in multiple places
fn process(data: Vec<String>) -> Vec<String> {
    let upper: Vec<String> = data.iter().map(|s| s.to_uppercase()).collect();
    upper // data dropped here
}

// Borrow when you only need to read
fn count_long(items: &[String], min_len: usize) -> usize {
    items.iter().filter(|s| s.len() >= min_len).count()
}

// Cow<str> for "borrow or own" flexibility
use std::borrow::Cow;
fn sanitize(input: &str) -> Cow<str> {
    if input.contains('<') {
        Cow::Owned(input.replace('<', "&lt;"))
    } else {
        Cow::Borrowed(input) // zero allocation when no change needed
    }
}
```

### Lifetime Annotations
```rust
// Named lifetime: output lives as long as input
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}

// Struct holding a reference
struct Parser<'a> {
    input: &'a str,
    pos: usize,
}

impl<'a> Parser<'a> {
    fn new(input: &'a str) -> Self {
        Self { input, pos: 0 }
    }

    fn remaining(&self) -> &'a str {
        &self.input[self.pos..]
    }
}
```

---

## Error Handling

### `thiserror` for Library Errors
```rust
use thiserror::Error;

#[derive(Error, Debug)]
pub enum AppError {
    #[error("Not found: {0}")]
    NotFound(String),

    #[error("Unauthorized")]
    Unauthorized,

    #[error("Validation failed: {field} — {message}")]
    Validation { field: String, message: String },

    #[error("Database error")]
    Database(#[from] sqlx::Error),

    #[error("IO error")]
    Io(#[from] std::io::Error),
}

// Result alias
pub type Result<T> = std::result::Result<T, AppError>;
```

### `anyhow` for Application Code
```rust
use anyhow::{Context, Result, bail, ensure};

async fn load_config(path: &str) -> Result<Config> {
    let content = tokio::fs::read_to_string(path)
        .await
        .with_context(|| format!("Failed to read config file: {path}"))?;

    let config: Config = serde_json::from_str(&content)
        .context("Config file is not valid JSON")?;

    ensure!(config.port > 1024, "Port must be > 1024, got {}", config.port);

    Ok(config)
}
```

### Axum Error Response
```rust
use axum::{http::StatusCode, response::{IntoResponse, Response}, Json};
use serde_json::json;

impl IntoResponse for AppError {
    fn into_response(self) -> Response {
        let (status, message) = match &self {
            AppError::NotFound(msg) => (StatusCode::NOT_FOUND, msg.clone()),
            AppError::Unauthorized => (StatusCode::UNAUTHORIZED, "Unauthorized".into()),
            AppError::Validation { field, message } => (
                StatusCode::UNPROCESSABLE_ENTITY,
                format!("{field}: {message}"),
            ),
            AppError::Database(_) | AppError::Io(_) => {
                tracing::error!(error = ?self, "Internal error");
                (StatusCode::INTERNAL_SERVER_ERROR, "Internal server error".into())
            }
        };
        (status, Json(json!({ "error": message }))).into_response()
    }
}
```

---

## Async with Tokio

### Main Entry Point
```rust
#[tokio::main]
async fn main() -> anyhow::Result<()> {
    tracing_subscriber::fmt()
        .with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
        .init();

    let config = load_config("config.json").await?;
    let app = build_app(config).await?;

    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?;
    tracing::info!("Listening on {}", listener.local_addr()?);
    axum::serve(listener, app).await?;

    Ok(())
}
```

### Concurrent Tasks
```rust
use tokio::task::JoinSet;

async fn fetch_all(ids: Vec<String>) -> Vec<Result<Data>> {
    let mut set = JoinSet::new();

    for id in ids {
        set.spawn(async move { fetch_one(&id).await });
    }

    let mut results = Vec::new();
    while let Some(res) = set.join_next().await {
        results.push(res.expect("task panicked"));
    }
    results
}

// Or use join! for fixed set of futures
async fn fetch_dashboard(user_id: &str) -> Result<Dashboard> {
    let (user, posts, stats) = tokio::try_join!(
        fetch_user(user_id),
        fetch_posts(user_id),
        fetch_stats(user_id),
    )?;
    Ok(Dashboard { user, posts, stats })
}
```

---

## Axum Web Backend

### App Setup with State
```rust
use axum::{Router, extract::State, routing::{get, post}};
use std::sync::Arc;

#[derive(Clone)]
pub struct AppState {
    pub db: sqlx::PgPool,
    pub config: Arc<Config>,
}

pub fn build_router(state: AppState) -> Router {
    Router::new()
        .route("/health", get(health_check))
        .nest("/api/v1", api_routes())
        .with_state(state)
        .layer(
            tower::ServiceBuilder::new()
                .layer(tower_http::trace::TraceLayer::new_for_http())
                .layer(tower_http::cors::CorsLayer::permissive())
        )
}

fn api_routes() -> Router<AppState> {
    Router::new()
        .route("/posts", get(list_posts).post(create_post))
        .route("/posts/:id", get(get_post).delete(delete_post))
}
```

### Handlers
```rust
use axum::{
    extract::{Path, Query, State, Json},
    http::StatusCode,
};
use serde::{Deserialize, Serialize};
use uuid::Uuid;

#[derive(Deserialize)]
pub struct ListQuery {
    page: Option<u32>,
    limit: Option<u32>,
}

#[derive(Serialize)]
pub struct PostResponse {
    id: Uuid,
    title: String,
    content: Option<String>,
}

pub async fn list_posts(
    State(state): State<AppState>,
    Query(query): Query<ListQuery>,
) -> Result<Json<Vec<PostResponse>>, AppError> {
    let page = query.page.unwrap_or(1).max(1);
    let limit = query.limit.unwrap_or(10).min(100);
    let offset = (page - 1) * limit;

    let posts = sqlx::query_as!(
        PostResponse,
        "SELECT id, title, content FROM posts WHERE published = true
         ORDER BY created_at DESC LIMIT $1 OFFSET $2",
        limit as i64,
        offset as i64,
    )
    .fetch_all(&state.db)
    .await?;

    Ok(Json(posts))
}

#[derive(Deserialize)]
pub struct CreatePost {
    title: String,
    content: Option<String>,
}

pub async fn create_post(
    State(state): State<AppState>,
    Json(body): Json<CreatePost>,
) -> Result<(StatusCode, Json<PostResponse>), AppError> {
    if body.title.trim().is_empty() {
        return Err(AppError::Validation {
            field: "title".into(),
            message: "must not be empty".into(),
        });
    }

    let post = sqlx::query_as!(
        PostResponse,
        "INSERT INTO posts (title, content) VALUES ($1, $2) RETURNING id, title, content",
        body.title,
        body.content,
    )
    .fetch_one(&state.db)
    .await?;

    Ok((StatusCode::CREATED, Json(post)))
}
```

---

## CLI with Clap

```rust
use clap::{Parser, Subcommand};

#[derive(Parser)]
#[command(name = "myapp", about = "My CLI tool", version)]
pub struct Cli {
    #[arg(short, long, default_value = "info")]
    pub log_level: String,

    #[command(subcommand)]
    pub command: Command,
}

#[derive(Subcommand)]
pub enum Command {
    /// Start the server
    Serve {
        #[arg(short, long, default_value = "3000")]
        port: u16,
    },
    /// Run database migrations
    Migrate,
    /// Generate a secret key
    GenKey,
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let cli = Cli::parse();
    match cli.command {
        Command::Serve { port } => serve(port).await?,
        Command::Migrate => migrate().await?,
        Command::GenKey => println!("{}", generate_key()),
    }
    Ok(())
}
```

---

## Performance Tips

```rust
// Pre-allocate when size is known
let mut vec = Vec::with_capacity(items.len());

// Use &str instead of String in hot paths
fn is_valid(s: &str) -> bool { /* no allocation */ }

// Avoid unnecessary clones — use references
fn process(items: &[Item]) -> usize { items.len() }

// Use rayon for CPU-bound parallelism
use rayon::prelude::*;
let sum: u64 = big_vec.par_iter().map(|x| expensive(x)).sum();

// Use bytes::Bytes for zero-copy HTTP bodies
use bytes::Bytes;
async fn handler() -> Bytes {
    Bytes::from_static(b"hello")
}

// Profile with cargo-flamegraph
// cargo install flamegraph
// cargo flamegraph --bin my-app
```

---

## Key Rules

1. **Prefer `Result<T, E>` over `unwrap()`** — only `unwrap()` in tests or truly infallible
2. **`thiserror` for libraries, `anyhow` for binaries**
3. **`Arc<T>` for shared state, `Mutex<T>` only when mutation needed**
4. **`tokio::spawn` for fire-and-forget, `JoinSet` for tracked tasks**
5. **`sqlx::query_as!` macro** — compile-time query validation
6. **Never block in async context** — use `tokio::task::spawn_blocking` for CPU work
7. **`tracing` not `println!`** — structured logging always
8. **Clippy is not optional** — `cargo clippy -- -D warnings` in CI
9. **`#[derive(Clone)]` on state** — Axum requires `Clone` on shared state
10. **Edition 2024** in `Cargo.toml` — use latest language features

