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
- Devolutions/IronRDPtheir own version, first seen Nov 1, 2026
- orin-dx/monokltheir own version, first seen Nov 1, 2026
- agent-ix/quointheir own version, first seen Jun 25, 2026
- paulnsorensen/hallouminateedited, first seen Jun 12, 2026
Other versions4 projects use a version of this.
Word for word · 0
None found.
Edited · 1
- paulnsorensen/hallouminate★ 099% same
Wrote their own version · 3
- Devolutions/IronRDP★ 3.3ksame name
- agent-ix/quoin★ 20same name
- 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.
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
- Very specific: line 12
Write for loops with mutable accumulators instead of iterator combinators.
- Very specific: line 58
Use let ... else to extract values and exit early on failure.
- Very specific: line 97
Use if let only when the Some/Ok branch is short and there's no else branch.
- Distinctive: line 150
No inline comments explaining what code does
- Very specific: line 151
No section headers or dividers (// --- Section ---)
- Distinctive: line 152
No TODO comments (use issue tracker)
- Distinctive: line 153
No commented-out code (use version control)
- Very specific: line 198
Use enums with meaningful variant names instead of bool parameters.
- Distinctive: line 228
Always match all variants explicitly to get compiler errors when variants are added.
- Distinctive: line 245
If a wildcard seems necessary, ask the user before using it.
- Very specific: line 249
Use full match expressions instead of matches!
- Distinctive: line 265
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.
- Distinctive: line 285
When behavior is specific to one command or runtime mode, derive that policy at the boundary and pass a narrow type inward.
- Very specific: line 308
Use names that describe the role of a value.
- Very specific: line 364
Use tracing::{trace, debug, info, warn, error}!
- Very specific: line 369
Test code (#[cfg(test)], tests/*.rs, examples).
- Very specific: line 414
If you genuinely need to write to a specific fd (not "log, please route somewhere"), use writeln!(io::stderr().lock(), ...) / …
- Distinctive: line 421
When searching or navigating Rust code, always use the LSP tool with rust-analyzer operations:
“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.
Files
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.rsuser-facing output (prompts, usage, final results). Even there, prefertracingwhen 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:
println!/eprintln!write tostdout/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 — seedocs/war-histories.md— "The eprintln! tty-backpressure stall".tracingintegrates with the project'sLogConfig/StructuredLoggerpipeline, gets trace-context propagation for free, and is filterable viaRUST_LOG/VOIDBOX_LOG_LEVELwithout a rebuild.tracingoutput 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 definedfindReferences- Find all references to a symbolhover- Get type info and documentationdocumentSymbol- Get all symbols in a filegoToImplementation- Find trait implementations
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
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.

