Skip to content
Back to skills

Rust Style

ASecurity

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.

  • 88 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added September 3, 2026
ai-agentsrustgoshellexpressbackenddocumentation

Works with

  • cli

Security analysis

A100/100

Scanned September 3, 2026

npx -y skills add the-void-ia/void-box --skill rust-style --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Rust Style?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Rust Style
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/the-void-ia-rust-style/badge)](https://www.skillsdirectory.com/skills/the-void-ia-rust-style)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: rust-style
description: 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.
---

# 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.

```rust
// 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();
```

```rust
// DO
let mut total = 0;
for value in values {
    total += value.amount();
}

// DON'T
let total: i64 = values.iter().map(|v| v.amount()).sum();
```

```rust
// 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.

```rust
// 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);
}
```

```rust
// 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.

```rust
// 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_`.

```rust
// 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)?;
```

```rust
// 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.

```rust
// 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.

```rust
// 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.

```rust
// DO
enum Visibility {
    Public,
    Private,
}

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

// DON'T
fn create_repo(name: &str, is_public: bool) { }
```

```rust
// 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.

```rust
// 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.

```rust
// 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.

```rust
// 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.

```rust
// 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.

```rust
// 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.

```rust
// 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.

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

// DON'T
process(user.id, user.name, user.email);
```

```rust
// 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.

```rust
// 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

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.