Skip to content
Development
Agent

rust-modern-code

Load this file before you write or edit any Rust code. Each rule below fixes a failure measured in generated code: code that compiled and passed its tests but used outdated or weak idioms. Rules marked with a version need that version or later in `Cargo.toml`; check the

BOOST
From plugin
vexjoy-agent
443198 skills198 agents12 commands79 hooks
Install
$ npx -y skills add notque/vexjoy-agent --agent claude-code

How it fires

How this agent gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.

Context preview

The summary Claude sees to decide when to auto-load this agent.

Load this file before you write or edit any Rust code. Each rule below fixes a failure measured in generated code: code that compiled and passed its tests but used outdated or weak idioms. Rules marked with a version need that version or later in `Cargo.toml`; check the

Agent definition

rust-modern-code.md

Writing Modern Rust (Rust 2024 Edition)

Load this file before you write or edit any Rust code. Each rule below fixes a failure measured in generated code: code that compiled and passed its tests but used outdated or weak idioms. Rules marked with a version need that version or later in `Cargo.toml`; check the `edition` and `rust-version` lines first.

grep -E '^edition|^rust-version' Cargo.toml
# "edition = "2024"" unlocks every rule here; lower editions: see rust-version-idioms.md

1. Doc comments on every public item

Every public type, function, method, constant, and module gets a doc comment that starts with a verb or noun phrase describing what the item does (not just repeating its name). Every `lib.rs` and `mod.rs` starts with a `//!` crate or module doc comment. Measured: 9 of 12 unguided outputs skipped doc comments; 5 of 14 guided outputs still skipped the module comment.

// BAD
pub struct UserRepo {
    pool: sqlx::PgPool,
}

impl UserRepo {
    pub fn new(pool: sqlx::PgPool) -> Self {
        Self { pool }
    }
}

// GOOD
//! User repository backed by PostgreSQL via sqlx.

/// Stores users in PostgreSQL. Safe for concurrent use via internal connection pooling.
pub struct UserRepo {
    pool: sqlx::PgPool,
}

impl UserRepo {
    /// Creates a `UserRepo` using the given connection pool. The caller owns and closes the pool.
    pub fn new(pool: sqlx::PgPool) -> Self {
        Self { pool }
    }
}

State thread-safety, ownership, and zero-value/default behavior when they matter.

2. Error handling

| Do | Not | Since | |----|-----|-------| | `thiserror` for library error types | `Box<dyn Error>` or `String` as error return in library code | stable | | `anyhow::Result` / `.context("...")` for application code | `.unwrap()` or `.expect("")` outside tests | stable | | `err.downcast_ref::<ConcreteError>()` / `err.is::<E>()` (anyhow) | String matching: `err.to_string().contains("not found")` | stable | | Named variants: `Error::NotFound { id }` | `Error::NotFound(String)` with formatted text as data | stable | | `.context("create user {email:?}")` to add call-site context | Empty wrappers: `anyhow!("error: {e}")` that repeat the message | stable | | Driver typed errors via `sqlx::Error::Database` downcast | Magic codes or string matching on SQLSTATE | stable |

Library errors with `thiserror`:

// BAD
pub fn get_user(id: Uuid) -> Result<User, Box<dyn std::error::Error>> { ... }

// GOOD
use thiserror::Error;

#[derive(Debug, Error)]
pub enum UserError {
    #[error("user {id} not found")]
    NotFound { id: Uuid },
    #[error("database error")]
    Database(#[from] sqlx::Error),
}

pub async fn get_user(id: Uuid, pool: &PgPool) -> Result<User, UserError> { ... }

Application code with `anyhow`:

// BAD
fn load_config(path: &str) -> Result<Config, Box<dyn Error>> {
    let s = std::fs::read_to_string(path)?;
    Ok(toml::from_str(&s)?)
}

// GOOD
use anyhow::{Context, Result};

fn load_config(path: &str) -> Result<Config> {
    let s = std::fs::read_to_string(path)
        .with_context(|| format!("read config from {path:?}"))?;
    toml::from_str(&s).context("parse config")
}

Driver errors — find the typed error in the crate source, then match it:

fn is_unique_violation(err: &sqlx::Error) -> bool {
    if let sqlx::Error::Database(db_err) = err {
        db_err.is_unique_violation()
    } else {
        false
    }
}

3. Async and task lifecycle

  • Use `#[tokio::test]` for all async tests. Never `tokio::runtime::Runtime::new().unwrap().block_on(...)` in tests.
  • Never `std::thread::sleep` in async tests — use `tokio::time::pause()` + `tokio::time::advance(d)`. Measured: 7 of 12 unguided outputs used `thread::sleep`.
  • Every `tokio::spawn` call stores the `JoinHandle` or awaits it. Fire-and-forget spawns that drop `JoinHandle` silently discard panics and make shutdown impossible.
  • Cancellation-safe async: hold no locks across `.await` points; use `tokio::sync::Mutex` not `std::sync::Mutex` in async code.
  • Signal handling: `tokio::signal::ctrl_c()` for Ctrl-C; `signal::unix::signal(SignalKind::terminate())` for SIGTERM.
// BAD: fire-and-forget, panic lost, no shutdown path
fn start_background_task() {
    tokio::spawn(async { do_work().await });
}

// GOOD: tracked handle, can be awaited on shutdown
pub struct Worker {
    handle: tokio::task::JoinHandle<()>,
}

impl Worker {
    pub fn start() -> Self {
        Self {
            handle: tokio::spawn(async { do_work().await }),
        }
    }

    pub async fn shutdown(self) {
        self.handle.await.expect("worker panicked");
    }
}

`main` delegates to `run`:

// BAD: unwrap in main, no graceful error display
#[tokio::main]
async fn main() {
    run().await.unwrap();
}

// GOOD
#[tokio::main]
async fn main() -> anyhow::Result<()> {
    run().await
}

async fn run() -> anyhow::Result<()> {
    let config = load_config("config.toml")?;
    // ... defers and signal setup here
    Ok(())
}

4. Bounded concurrency

Use `tokio::task::JoinSet` for a bounded pool that collects results. Results go back in completion order; use a preallocated `Vec` indexed by task ID when order matters.

/// Map calls fn for every item with at most `workers` tasks in flight.
/// Returns results in the order tasks complete, not input order.
pub async fn map_concurrent<T, R, F, Fut>(
    items: Vec<T>,
    workers: usize,
    f: F,
) -> anyhow::Result<Vec<R>>
where
    T: Send + 'static,
    R: Send + 'static,
    F: Fn(T) -> Fut + Clone + Send + 'static,
    Fut: Future<Output = anyhow::Result<R>> + Send,
{
    use tokio::task::JoinSet;
    let semaphore = Arc::new(tokio::sync::Semaphore::new(workers));
    let mut set = JoinSet::new();

    for item in items {
        let sem = semaphore.clone();
        let f = f.clone();
        set.spawn(async move {
            let _permit = sem.acquire().await?;
            f(item).await
        });
    }
Read more
Ships withvexjoy-agent

Essays and writing behind this toolkit live at vexjoy.com. VexJoy Agent connects plain-English requests to specialist agents, skills, and workflows. /do selects the knowledge and tools needed for your task.

Get the whole plugin

Other agents on vexjoy-agent.