agentleFS
the-void-iathe-void-ia/void-box/.claude/skills/rust-style/SKILL.md

rust-style

Skill88 starsChanged 3 months agoApache-2.0View on GitHub

Rust coding style guide. Apply automatically when writing or modifying Rust code. Enforces for-loops over iterators, let-else for early returns, variable shadowing, newtypes, explicit matching, and minimal comments.

4 other projects use a version of this, 1 of them edited.

Adopted by

Most recently taken up by

  • Other versions4 projects use a version of this.

    Word for word · 0

    None found.

    Edited · 1

    1. paulnsorensen/hallouminate★ 099% same

    Wrote their own version · 3

    1. Devolutions/IronRDP★ 3.3ksame name
    2. agent-ix/quoin★ 20same name
    3. orin-dx/monokl★ 1same name

    How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.

  • In this repository7 other files it gives its agents.

The file

Show its rules and text

We split this skill into the rules it gives an agent

18 rules. 0 are common, 8 are distinctive, and 10 are very specific to this project.

  • 0

    Common

    Most of the 4 edited versions we read keep these.

  • 8

    Distinctive

    This author's own choices. Worth a look.

  • 10

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Write for loops with mutable accumulators instead of iterator combinators.

    line 12
  2. Very specific:

    Use let ... else to extract values and exit early on failure.

    line 58
  3. Very specific:

    Use if let only when the Some/Ok branch is short and there's no else branch.

    line 97
  4. Distinctive:

    No inline comments explaining what code does

    line 150
  5. Very specific:

    No section headers or dividers (// --- Section ---)

    line 151
  6. Distinctive:

    No TODO comments (use issue tracker)

    line 152
  7. Distinctive:

    No commented-out code (use version control)

    line 153
  8. Very specific:

    Use enums with meaningful variant names instead of bool parameters.

    line 198
  9. Distinctive:

    Always match all variants explicitly to get compiler errors when variants are added.

    line 228
  10. Distinctive:

    If a wildcard seems necessary, ask the user before using it.

    line 245
  11. Very specific:

    Use full match expressions instead of matches!

    line 249
  12. Distinctive:

    If a feature has a small closed set of modes, model it as an enum instead of combining booleans and options that create ambiguous states.

    line 265
  13. Distinctive:

    When behavior is specific to one command or runtime mode, derive that policy at the boundary and pass a narrow type inward.

    line 285
  14. Very specific:

    Use names that describe the role of a value.

    line 308
  15. Very specific:

    Use tracing::{trace, debug, info, warn, error}!

    line 364
  16. Very specific:

    Test code (#[cfg(test)], tests/*.rs, examples).

    line 369
  17. Very specific:

    If you genuinely need to write to a specific fd (not "log, please route somewhere"), use writeln!(io::stderr().lock(), ...) / …

    line 414
  18. Distinctive:

    When searching or navigating Rust code, always use the LSP tool with rust-analyzer operations:

    line 421

“Common” is measured: kept by at least 6 in 10 of the 4 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Rust Coding Style

Apply these rules when writing or modifying any Rust code.

Control Flow: Use for Loops, Not Iterator Chains

Write for loops with mutable accumulators instead of iterator combinators.

// DO
let mut results = Vec::new();
for item in items {
    if item.is_valid() {
        results.push(item.process());
    }
}

// DON'T
let results: Vec<_> = items
    .iter()
    .filter(|item| item.is_valid())
    .map(|item| item.process())
    .collect();
// DO
let mut total = 0;
for value in values {
    total += value.amount();
}

// DON'T
let total: i64 = values.iter().map(|v| v.amount()).sum();
// DO
let mut found = None;
for item in items {
    if item.matches(query) {
        found = Some(item);
        break;
    }
}

// DON'T
let found = items.iter().find(|item| item.matches(query));

Early Returns: Use let ... else

Use let ... else to extract values and exit early on failure. This keeps the happy path unindented.

// DO
let Some(user) = get_user(id) else {
    return Err(Error::NotFound);
};
let Ok(session) = user.active_session() else {
    return Err(Error::NoSession);
};
// continue with user and session

// DON'T
if let Some(user) = get_user(id) {
    if let Ok(session) = user.active_session() {
        // deeply nested code
    } else {
        return Err(Error::NoSession);
    }
} else {
    return Err(Error::NotFound);
}
// DO
let Some(value) = maybe_value else { continue };
let Ok(parsed) = input.parse::<i32>() else { continue };

// DON'T
if let Some(value) = maybe_value {
    if let Ok(parsed) = input.parse::<i32>() {
        // ...
    }
}

Pattern Matching: Minimize if let

Use if let only when the Some/Ok branch is short and there's no else branch.

// ACCEPTABLE: short action, no else
if let Some(callback) = self.on_change {
    callback();
}

// DO: use let-else when you need the value
let Some(config) = load_config() else {
    return default_config();
};

// DO: use match for multiple cases
match result {
    Ok(value) => process(value),
    Err(Error::NotFound) => use_default(),
    Err(e) => return Err(e),
}

Variable Naming: Shadow, Don't Rename

Shadow variables through transformations. Avoid prefixes like raw_, parsed_, trimmed_.

// DO
let input = get_raw_input();
let input = input.trim();
let input = input.to_lowercase();
let input = parse(input)?;

// DON'T
let raw_input = get_raw_input();
let trimmed_input = raw_input.trim();
let lowercase_input = trimmed_input.to_lowercase();
let parsed_input = parse(lowercase_input)?;
// DO
let path = args.path;
let path = path.canonicalize()?;
let path = path.join("config.toml");

// DON'T
let input_path = args.path;
let canonical_path = input_path.canonicalize()?;
let config_path = canonical_path.join("config.toml");

Comments: Don't Write Them

  • No inline comments explaining what code does
  • No section headers or dividers (// --- Section ---)
  • No TODO comments (use issue tracker)
  • No commented-out code (use version control)

Exception: Doc comments (///) on public items are required. See the rustdoc skill.

// DON'T
// Check if user is valid
if user.is_valid() {
    // Update the timestamp
    user.touch();
}

// --- Helper functions ---

// TODO: refactor this later
fn helper() { }

// Old implementation:
// fn old_way() { }

// DO
if user.is_valid() {
    user.touch();
}

fn helper() { }

Type Safety: Prefer Newtypes Over Strings

Wrap strings in newtypes to add semantic meaning and prevent mixing different string types.

// DO
struct UserId(String);
struct Email(String);

fn send_email(to: Email, from: UserId) { }

// DON'T
fn send_email(to: String, from: String) { }

Type Safety: Prefer Strongly-Typed Enums Over Bools

Use enums with meaningful variant names instead of bool parameters.

// DO
enum Visibility {
    Public,
    Private,
}

fn create_repo(name: &str, visibility: Visibility) { }

// DON'T
fn create_repo(name: &str, is_public: bool) { }
// DO
enum Direction {
    Forward,
    Backward,
}

fn traverse(dir: Direction) { }

// DON'T
fn traverse(forward: bool) { }

Pattern Matching: Never Use Wildcard Matches

Always match all variants explicitly to get compiler errors when variants are added.

// DO
match status {
    Status::Pending => handle_pending(),
    Status::Active => handle_active(),
    Status::Completed => handle_completed(),
}

// DON'T
match status {
    Status::Pending => handle_pending(),
    _ => handle_other(),
}

If a wildcard seems necessary, ask the user before using it.

Pattern Matching: Avoid matches! Macro

Use full match expressions instead of matches!. Full matches provide better compiler diagnostics when the matched type changes.

// DO
let is_ready = match state {
    State::Ready => true,
    State::Pending => false,
    State::Failed => false,
};

// DON'T
let is_ready = matches!(state, State::Ready);

Data Models: Prefer Explicit Enums Over Overlapping Flags

If a feature has a small closed set of modes, model it as an enum instead of combining booleans and options that create ambiguous states.

// DO
enum GuestConsoleSink {
    Disabled,
    Stderr,
    File(PathBuf),
}

// DON'T
struct GuestConsoleConfig {
    attach_guest_console: bool,
    guest_console_log: Option<PathBuf>,
}

Policy Boundaries: Keep Execution Policy Out of Persisted Config

When behavior is specific to one command or runtime mode, derive that policy at the boundary and pass a narrow type inward. Do not mutate resolved config just to express command-scoped behavior.

// DO
enum TracingMode {
    Standard,
    InteractivePty,
}

let mode = tracing_mode_for_command(&cli.command);
init_tracing(mode, &config);

// DON'T
if matches!(cli.command, Command::Shell { .. }) {
    config.log_to_stderr = false;
}
init_tracing(&config);

Naming: Prefer Meaningful Locals Over Shorthand

Use names that describe the role of a value. Avoid short names like rc, n, msg, ws, k, and v outside tiny, obvious scopes.

// DO
let ready_count = poll(...)?;
let bytes_read = read(...)?;
let winsize = tcgetwinsize(...)?;

// DON'T
let rc = poll(...)?;
let n = read(...)?;
let ws = tcgetwinsize(...)?;

Constants: Name Repeated or Meaningful Literals

Lift repeated or behavior-defining literals into documented module-scope constants instead of keeping them inline.

// DO
/// Poll timeout for the interactive PTY relay loop.
const PTY_POLL_TIMEOUT_MS: i32 = 100;

// DON'T
poll(&mut pollfds, 100)?;

Destructuring: Always Use Explicit Destructuring

Destructure structs and tuples explicitly to get compiler errors when fields change.

// DO
let User { id, name, email } = user;
process(id, name, email);

// DON'T
process(user.id, user.name, user.email);
// DO
for Entry { key, value } in entries {
    map.insert(key, value);
}

// DON'T
for entry in entries {
    map.insert(entry.key, entry.value);
}

Logging: Never println! / eprintln! in Library or Service Code

Use tracing::{trace, debug, info, warn, error}! for all runtime output in library crates, long-running services (daemons, guest-agent, backends), and anything that runs in production. println! and eprintln! are acceptable only in:

  • Test code (#[cfg(test)], tests/*.rs, examples).
  • The top-level CLI main.rs user-facing output (prompts, usage, final results). Even there, prefer tracing when structured logging is meaningful.
// DO (library / service code)
use tracing::{debug, info, warn};

fn handle_request(req: Request) {
    info!(request_id = req.id, "handling request");
    if req.is_stale() {
        warn!(request_id = req.id, "stale request, rejecting");
        return;
    }
    debug!(?req, "request details");
}

// DON'T (library / service code)
fn handle_request(req: Request) {
    println!("handling request {}", req.id);
    if req.is_stale() {
        eprintln!("stale request {}", req.id);
        return;
    }
    eprintln!("request details: {req:?}");
}

Why this rule is absolute in service code:

  1. println! / eprintln! write to stdout / stderr. In a long-running service those fds may be pointed at a serial tty (guest-agent is PID 1 with stderr → /dev/console), a pipe to another process, or a file. Each of those paths has its own backpressure semantics. eprintln! inside a hot-path helper wedged PID 1 for two days of investigation — see docs/war-histories.md — "The eprintln! tty-backpressure stall".
  2. tracing integrates with the project's LogConfig / StructuredLogger pipeline, gets trace-context propagation for free, and is filterable via RUST_LOG / VOIDBOX_LOG_LEVEL without a rebuild.
  3. tracing output can be routed to any sink (file, stderr, ringbuffer, OTLP) without touching call sites. println! can't.

If you genuinely need to write to a specific fd (not "log, please route somewhere"), use writeln!(io::stderr().lock(), ...) / writeln!(io::stdout().lock(), ...) with an explicit lock so the intent is obvious and the lint doesn't catch it.

Code Navigation: Always Use rust-analyzer LSP

When searching or navigating Rust code, always use the LSP tool with rust-analyzer operations:

  • goToDefinition - Find where a symbol is defined
  • findReferences - Find all references to a symbol
  • hover - Get type info and documentation
  • documentSymbol - Get all symbols in a file
  • goToImplementation - Find trait implementations

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.