Agent skill

Rust Idioms

by irahardianto in irahardianto/awesome-agv

Rust idioms: ownership and borrow checker patterns, error handling with thiserror/anyhow, Tokio async concurrency, lifetime management, and Clippy pedantic compliance.

MITAuto-check passedDevelopment

Install Rust Idioms

skills CLI
$ npx skills add irahardianto/awesome-agv --skill rust-idioms -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install irahardianto/awesome-agv rust-idioms --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/irahardianto/awesome-agv.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.agents/skills/rust-idioms .claude/skills/rust-idioms && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
rust-idioms
GitHub stars
156
Token cost
~6.8k tokens
SKILL.md length
2,324 words
Files
6 (incl. references)
Skills in repo
34
Repo updated
First seen
Licence
MIT

At a glance

Rust idioms: ownership and borrow checker patterns, error handling with thiserror/anyhow, Tokio async concurrency, lifetime management, and Clippy pedantic compliance.

  • Works in 4 steps: Prefer borrowing (&T, &mut T) over cloning → Minimize owned data in structs → Avoid unnecessary Arc> → …
  • Reviewing Rust crates
  • Calls cargo

What it does

Rust Idioms is an agent skill from irahardianto/awesome-agv. Rust idioms: ownership and borrow checker patterns, error handling with thiserror/anyhow, Tokio async concurrency, lifetime management, and Clippy pedantic compliance. Use when writing, optimizing, or reviewing Rust crates.

Its SKILL.md is about 6.8k tokens, which your agent loads only when the skill is triggered. The skill folder holds 6 other files, including reference files (for example `references/project-structure.md`, `references/recommended-dependencies.md` and `references/rust-patterns-and-anti-patterns.md`).

It sits in Development. It works with Rust. The repository describes itself as: Comprehensive sets of standards and practices designed to elevate the capabilities of AI coding agents. The licence is MIT.

When your agent uses it

  • Reviewing Rust crates

Example prompts

  • “/rust-idioms”

Workflow steps

4 steps, taken from the first numbered list in SKILL.md.

  1. Prefer borrowing (&T, &mut T) over cloning
  2. Minimize owned data in structs
  3. Avoid unnecessary Arc>
  4. Respect the Copy / Clone boundary

What it can do on your machine

Read from SKILL.md and the folder at commit 9e997ba. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    Shell commands in SKILL.md call:

    • cargo

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    No URLs in SKILL.md.

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

Rust Idioms loads about 6.8k tokens when it runs, and up to ~23k if it reads all its reference files. Until then it costs about 59 tokens; SKILL.md has 2,324 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~59
When it runs · the whole SKILL.md, loaded when a task matches
~6.8k
With references · SKILL.md plus every file in references/, read only if the agent opens them
~23k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check passed

The automated check found no risky patterns in SKILL.md.

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.

SKILL.md

The full file from irahardianto/awesome-agv at commit 9e997ba, republished under its MIT licence (© irahardianto). 2,324 words, ~6,840 tokens.

Download SKILL.mdSave it as .claude/skills/rust-idioms/SKILL.md (or your agent's skills folder). This skill also uses 5 other files; get the full folder from GitHub.
name
rust-idioms
description
Rust idioms: ownership and borrow checker patterns, error handling with thiserror/anyhow, Tokio async concurrency, lifetime management, and Clippy pedantic compliance. Use when writing, optimizing, or reviewing Rust crates.

Rust Idioms and Patterns

Core Philosophy

Rust's type system and ownership model are your primary tools for correctness. Lean into the compiler — it is your strongest ally. Write code that is idiomatic, safe, and expressive.

Scope: This file covers Rust-specific coding idioms. For file layout, see references/project-structure.md (in this skill). For detailed safety, SAST security invariants, and performance anti-patterns, see references/rust-patterns-and-anti-patterns.md (in this skill). For Rust test naming and conventions, see §Testing below; for universal testing principles, see @.agents/rules/testing-strategy.md. For logging library choice and setup, see @.agents/skills/logging-implementation/SKILL.md.

Toolchain and Minimum Supported Rust Version

Default to the latest stable Rust. As of July 2026, this is Rust 1.97. All guidance in this skill assumes latest stable features. When creating new projects, set rust-version in Cargo.toml to prevent builds on outdated toolchains.

toml
[package]
edition = "2024"
rust-version = "1.97"

Key version milestones that affect this skill:

  • 1.75+ — Native async fn in traits (no async_trait crate needed for static dispatch). This milestone is the single source of truth for the async-trait crate policy:
    • Prefer static dispatch — impl MyTrait return params or generic T: MyTrait bounds use native async fn in traits; no crate required and zero dispatch overhead.
    • Use the async-trait crate only when dynamic dispatch via dyn Trait is explicitly required — e.g. Box<dyn MyTrait>, Arc<dyn MyTrait> for runtime polymorphism, object-safe trait objects, or storing heterogeneous trait impls in a collection.
    • Never add async-trait as a default dependency just for ergonomics — it adds a heap allocation and dynamic dispatch cost. Add it only for the specific crates/traits that need dyn dispatch.
  • 1.74+ — Workspace lint inheritance ([workspace.lints])
  • 1.63+ — Mutex::new() in const context (no OnceCell wrapper needed)

For recommended crate versions and starter Cargo.toml, see references/recommended-dependencies.md.

Ownership and Borrowing
  1. Prefer borrowing (&T, &mut T) over cloning

    • Never .clone() to silence the borrow checker without a // CLONE: comment explaining why
    • Use Cow<'_, T> when a function may or may not need ownership
    • Prefer &str over String in function parameters, &[T] over Vec<T>
  2. Minimize owned data in structs

    • Use references with explicit lifetimes when the struct is short-lived
    • Use owned types (String, Vec<T>) when the struct must outlive its inputs
  3. Avoid unnecessary Arc<Mutex<T>>

    • If data flows one direction, use channels (tokio::sync::mpsc)
    • If data is read-heavy, consider RwLock over Mutex
    • If data is immutable after init, use Arc<T> without a lock
  4. Respect the Copy / Clone boundary:

    • Never call .clone() on types that implement Copy (e.g., i32, f64, bool, char, usize, Option<CopyType>)
    • Copy types are implicitly copied on assignment — .clone() is misleading and suggests heap allocation
    • When unsure, check: Copy = bitwise copy (stack only); Clone = potentially expensive deep copy
    rust
    // ❌ Misleading — usize implements Copy
    let count = other_count.clone();
    
    // ✅ Implicit copy — clear and correct
    let count = other_count;
Error Handling
  1. Use the ? operator for propagation — never unwrap() in production code

    • unwrap() and expect() are acceptable only in:
      • Tests (#[test], #[tokio::test])
      • Infallible operations where the invariant is proven (document with // SAFETY: comment)
      • CLI main() function with clear error messages via expect("reason")
  2. Choose error crates by context:

    ContextCrateReason
    Library cratesthiserrorTyped, matchable errors. Callers need to handle specific variants.
    Web service HTTP errorsthiserrorAppError enum must implement IntoResponse — typed variants required.
    Service/domain layer errorsthiserrorDomain errors need structured variants for logging and client responses.
    Application glue / scripts / CLIanyhowError type doesn't matter; ergonomic propagation is all you need.

    Web service rule: Use thiserror for AppError (HTTP handler errors) and domain errors. anyhow::Error does NOT implement IntoResponse and cannot be returned from Axum handlers. Use anyhow only in non-HTTP utility code (scripts, migration runners, CLI entrypoints) where errors are printed, not sent over the wire.

    The idiomatic pattern is thiserror for typed variants + #[from] anyhow::Error as the catch-all Internal variant in AppError. See axum-idioms/SKILL.md §Error Handling for the complete pattern.

    Never add anyhow as a dependency to library crates — it leaks a concrete error type into your public API.

  3. Error type design:

rust
// ✅ Good — typed, matchable errors
#[derive(Debug, thiserror::Error)]
pub enum PathfinderError {
    #[error("file not found: {path}")]
    FileNotFound { path: PathBuf },
    #[error("AST parse failed: {0}")]
    ParseError(String),
    #[error(transparent)]
    Io(#[from] std::io::Error),
}

// ❌ Bad — stringly-typed, unmatchable
fn do_thing() -> Result<(), String> { ... }

// ✅ Use #[must_use] on functions returning non-Result types that callers must handle
#[must_use]
pub fn compute_checksum(data: &[u8]) -> u64 { ... }

// ℹ️ Result<T, E> already has #[must_use] in std — adding it to Result-returning
// functions is redundant. The compiler warns on unused Result values automatically.
pub fn create_task(req: CreateTaskRequest) -> Result<Task, TaskError> { ... }
  1. Use lazy evaluation for fallback values:

    • unwrap_or_else(|| expr) instead of unwrap_or(expr) when the fallback involves a function call
    • expect messages should be string literals, not format!() calls
    • map_or_else instead of map_or when either branch involves computation
    rust
    // ❌ Eager — default_value() is called even when result is Ok
    let val = result.unwrap_or(default_value());
    let msg = result.expect(&format!("failed for {id}"));
    
    // ✅ Lazy — default_value() only called when needed
    let val = result.unwrap_or_else(|_| default_value());
    let msg = result.unwrap_or_else(|e| panic!("failed for {id}: {e}"));
Async and Concurrency
  1. Use tokio as the async runtime

    • Mark async entry points with #[tokio::main] or #[tokio::test]
    • Prefer tokio::spawn for concurrent tasks, not std::thread::spawn
    • Use tokio::select! for racing futures, not manual polling
  2. Cancellation safety:

    • Prefer tokio::sync::mpsc over tokio::sync::broadcast unless fan-out is needed
    • Document cancellation behavior on any async fn that holds resources across .await
    • Use tokio_util::sync::CancellationToken for graceful shutdown
  3. Blocking operations:

    • Never call blocking I/O inside async context
    • Use tokio::task::spawn_blocking for CPU-heavy or blocking work
    • Use tokio::fs instead of std::fs inside async functions
  4. Use tracing instead of log for all structured diagnostics in async applications:

    • tracing is span-aware — log entries inherit context from parent spans (correlation IDs, request metadata)
    • log is fire-and-forget with no span concept — unsuitable for async where context flows across .await boundaries
    • Use #[tracing::instrument] on async functions to automatically create spans with function arguments
    • See @.agents/skills/logging-implementation/SKILL.md §Rust for the full setup
Unsafe Code
  1. Zero unsafe blocks unless in FFI boundaries

    • Tree-sitter C bindings and similar FFI are the only valid use case
    • Every unsafe block must have a // SAFETY: comment explaining the invariant
  2. Minimize unsafe surface area:

    • Encapsulate unsafe in a safe wrapper function
    • The wrapper's public API must be safe to call from any context
    • Write tests that exercise the boundary conditions of unsafe wrappers
  3. Never use unsafe to bypass the borrow checker — restructure the code instead

Lifetimes and Generics
  1. Prefer '_ lifetime elision when possible

    • Only introduce named lifetimes when the compiler requires them or when they clarify intent
    • Use 'a for single lifetime parameters, descriptive names ('input, 'query) for multiple
  2. Keep generic bounds simple:

    • Prefer concrete types for prototyping, introduce generics when the pattern stabilizes
    • Use impl Trait in argument position for simple cases
    • Use where clauses for complex bounds — never inline complex bounds in <...>
  3. Avoid lifetime gymnastics:

    • If lifetime annotations become complex, restructure to use owned data or Arc
    • Consider the "split borrow" pattern to avoid borrow checker issues in struct methods
Idiomatic Patterns
  1. Builder pattern for types with many optional fields:

    • Return Self from builder methods for chaining
    • build() returns Result<T, BuildError>, not T
  2. Newtype pattern for domain types:

    • Wrap primitives: struct UserId(u64), not bare u64
    • Implement Deref only when the newtype truly "is-a" the inner type
  3. Typestate pattern for state machines:

    • Different states = different types — invalid transitions are compile errors
    • Use this for protocol implementations and lifecycle management
  4. From/Into conversions:

    • Implement From<A> for B (never Into directly)
    • Use impl From<X> for Error with thiserror's #[from] attribute
  5. Prefer T::new() over Default::default() for known types:

    • Use Vec::new(), String::new(), HashMap::new() — explicit, readable, idiomatic
    • Use Default::default() in generic contexts where T: Default bounds are needed
    • Use Default::default() in struct update syntax: MyStruct { field: value, ..Default::default() }
    rust
    // ✅ Idiomatic — explicit constructor for known types
    let items: Vec<String> = Vec::new();
    let name = String::new();
    let map: HashMap<String, i32> = HashMap::new();
    
    // ✅ Also good — capacity hint is valuable
    let items = Vec::with_capacity(100);
    
    // ✅ Default::default() in generic code — correct usage
    fn create_collection<T: Default>() -> T {
        T::default()
    }
    
    // ✅ Default::default() in struct update syntax
    let config = ServerConfig {
        port: 8080,
        ..Default::default()
    };
  6. Use stdlib convenience methods — avoid manual reimplementations:

    • str.split_once(pat) instead of manual splitn(2, pat) + indexing
    • a.min(b) / a.max(b) / a.clamp(lo, hi) instead of match a.cmp(&b) { ... }
    • Return expressions directly — don't bind to a variable and immediately return it
    • Use Ordering::then() / Ordering::then_with() for multi-field comparisons
    rust
    // ❌ Manual reimplementation
    let parts: Vec<&str> = s.splitn(2, ':').collect();
    let key = parts[0];
    let value = parts.get(1).unwrap_or(&"");
    
    // ✅ Idiomatic — clearer intent, less code
    let (key, value) = s.split_once(':').unwrap_or((s, ""));
    
    // ❌ Redundant match over Ordering
    match a.cmp(&b) {
        Ordering::Less | Ordering::Equal => a,
        Ordering::Greater => b,
    }
    
    // ✅ Direct
    a.min(b)
    
    // ❌ Redundant let-binding
    let result = compute_something();
    result
    
    // ✅ Return directly
    compute_something()
  7. Keep function complexity low (cyclomatic complexity < 10):

    • Functions exceeding this threshold must be decomposed
    • Common decomposition patterns for complex Rust functions:
      • Extract match arms into named helper functions
      • Use early returns (if !condition { return Err(...) }) to flatten nesting
      • Extract iterator chains with complex closures into named functions
      • Use the "parse, don't validate" pattern — convert unstructured data into typed structs early
    rust
    // ❌ High complexity — nested match + conditionals
    fn process(input: &Input) -> Result<Output> {
        match input.kind {
            Kind::A => {
                if input.flag {
                    // 20 lines...
                } else {
                    // 20 lines...
                }
            }
            Kind::B => { /* another 30 lines */ }
        }
    }
    
    // ✅ Decomposed — each function has single responsibility
    fn process(input: &Input) -> Result<Output> {
        match input.kind {
            Kind::A => process_kind_a(input),
            Kind::B => process_kind_b(input),
        }
    }
Testing
  1. Test organization (Rust-specific — differs from Go/TS):

    For the authoritative test layout rules (unit vs integration vs e2e placement, #[cfg(test)] conventions, tests/common/mod.rs pattern, #[tokio::test] usage), see references/project-structure.md §Testing Layout. The rules are co-located there to stay in sync with the directory layout they describe.

  2. Test naming: fn test_<function>_<scenario>_<expected>() (snake_case)

  3. Assertions:

    • Use assert_eq! / assert_ne! over assert!(a == b) — better error messages
    • Use assert!(matches!(result, Ok(_))) for enum variant checking
    • Never use assert!(true) or assert!(false):
      • assert!(false) / debug_assert!(false) → use unreachable!("reason") or panic!("reason")
      • assert!(true) → remove entirely (it tests nothing)
      • These are dead-code signals that should use proper constructs
  4. Property testing: Use proptest (preferred) or quickcheck for functions with wide input spaces. proptest is preferred for its superior strategy composability, automatic shrinking, and more expressive generators.

  5. Test coverage is non-negotiable for new code:

    • Every new pub fn, pub struct method, and impl block MUST have at least one test
    • Every new branch (if/else, match arm, error path) MUST be exercised by a test
    • When modifying existing code, add tests for the modified paths if none exist
    • Never leave a function untested with the intent to "add tests later"
    • Use cargo tarpaulin or cargo llvm-cov to verify coverage locally before committing
    bash
    # Quick coverage check during development
    cargo tarpaulin --workspace --skip-clean --out stdout
    
    # Generate detailed report
    cargo llvm-cov --workspace --lcov --output-path lcov.info
  6. Test double selection — choose the right tool:

    ApproachWhen to UseCrate
    Hand-written fakeSimple trait, few methods, test needs custom stateful behaviorNone (implement trait directly)
    mockallComplex trait, need to verify call counts, argument matching, or call orderingmockall
    Parameterized testsSame logic, multiple input/output pairs (like Go table-driven tests)rstest
    Snapshot testingLarge outputs (JSON responses, CLI output, error messages)insta
    rust
    // ✅ Hand-written fake — simple, debuggable, no macro magic
    struct FakeTaskStorage {
        tasks: HashMap<String, Task>,
    }
    impl TaskStorage for FakeTaskStorage {
        async fn get_by_id(&self, id: &str) -> Result<Task, StorageError> {
            self.tasks.get(id).cloned().ok_or(StorageError::NotFound)
        }
    }
    
    // ✅ mockall — when you need interaction verification
    #[cfg(test)]
    mock! {
        pub TaskStore {}
        impl TaskStorage for TaskStore {
            async fn get_by_id(&self, id: &str) -> Result<Task, StorageError>;
            async fn create(&self, task: &Task) -> Result<(), StorageError>;
        }
    }
    
    #[tokio::test]
    async fn test_service_calls_storage_once() {
        let mut mock = MockTaskStore::new();
        mock.expect_create()
            .times(1)
            .returning(|_| Ok(()));
        let service = TaskService::new(mock);
        service.create_task(request).await.unwrap();
    }
    
    // ✅ rstest — parameterized test cases
    use rstest::rstest;
    
    #[rstest]
    #[case("valid@email.com", true)]
    #[case("no-at-sign", false)]
    #[case("", false)]
    fn test_email_validation(#[case] input: &str, #[case] expected: bool) {
        assert_eq!(is_valid_email(input), expected);
    }
    
    // ✅ insta — snapshot testing for complex outputs
    use insta::assert_json_snapshot;
    
    #[test]
    fn test_task_response_shape() {
        let response = TaskResponse::from(sample_task());
        assert_json_snapshot!(response);
    }

    Prefer hand-written fakes for core domain traits — they are easier to debug and don't couple tests to implementation details. Use mockall only when the trait has many methods or you genuinely need interaction verification (call counts, argument matching, call ordering). Over-mocking with mockall leads to brittle tests that break on implementation changes.

Show full SKILL.md (1,016 more words)Show less
Clippy and Formatting
  1. cargo check for fast iteration during development

    • cargo check: type-checks without producing a binary — fastest feedback loop
    • cargo clippy: includes cargo check plus lint rules — use before committing
    • cargo build: only when you need the actual binary/library artifact
    • Never run cargo build during TDD cycles — it is significantly slower than cargo check
  2. cargo clippy must pass with zero warnings before any commit

  3. Clippy suppression policy — fix the code, don't silence the lint:

    NEVER suppress these lints — they signal structural problems that must be fixed:

    LintWhat It SignalsWhat To Do Instead
    too_many_linesFunction is monolithicDecompose into smaller functions (see Idiomatic Patterns §7)
    cognitive_complexityToo many branches/nestingFlatten with early returns, extract match arms
    too_many_argumentsFunction has too many paramsIntroduce a params/config struct or builder
    type_complexityNested generics are unreadableCreate a type alias or newtype wrapper
    struct_excessive_boolsStruct has too many boolean fieldsReplace with an enum, bitflags, or config sub-struct
    large_enum_variantEnum variant is disproportionately largeBox the large variant's payload

    Decomposition strategies (use INSTEAD of #[allow]):

    rust
    // ❌ FORBIDDEN — agent took the lazy path
    #[allow(clippy::too_many_lines)]
    fn process_request(req: &Request) -> Result<Response> {
        // 200 lines of code...
    }
    
    // ✅ REQUIRED — decompose the function
    fn process_request(req: &Request) -> Result<Response> {
        let validated = validate_request(req)?;
        let enriched = enrich_with_context(&validated)?;
        build_response(&enriched)
    }
    
    // ❌ FORBIDDEN — too many arguments
    #[allow(clippy::too_many_arguments)]
    fn create_server(host: &str, port: u16, tls: bool, timeout: u64,
                     max_conn: usize, log_level: &str, cert: &Path) -> Server { ... }
    
    // ✅ REQUIRED — params struct
    struct ServerConfig {
        host: String,
        port: u16,
        tls: bool,
        timeout: Duration,
        max_connections: usize,
        log_level: Level,
        cert_path: PathBuf,
    }
    fn create_server(config: ServerConfig) -> Server { ... }
    
    // ❌ FORBIDDEN — hiding type complexity
    #[allow(clippy::type_complexity)]
    fn get_handlers() -> HashMap<String, Box<dyn Fn(&Request) -> Pin<Box<dyn Future<Output = Response>>>>> { ... }
    
    // ✅ REQUIRED — type alias
    type HandlerFn = Box<dyn Fn(&Request) -> Pin<Box<dyn Future<Output = Response>>>>;
    fn get_handlers() -> HashMap<String, HandlerFn> { ... }

    Acceptable suppressions (with mandatory // ALLOW: comment):

    LintWhen Acceptable
    unwrap_usedIn #[cfg(test)] modules only
    expect_usedIn #[cfg(test)] modules, OR with a // SAFETY: comment proving infallibility, OR in a CLI main() that owns the process exit (clear message + exit code). This reconciles with the expect_used = "warn" lint level in recommended-dependencies.md — warn permits these uses while still surfacing every other expect() for review.
    module_name_repetitionsWhen the repetition is intentional API design
    must_use_candidateOn internal functions where the caller pattern is known
    missing_errors_docTemporarily during development (must be resolved before merge)
    needless_pass_by_valueWhen API stability requires it (with comment explaining why)
    items_after_statementsWhen locality of helper functions improves readability
    cast_possible_truncationWith bounds check or range validation immediately preceding the cast

    Rule of thumb: If you're about to write #[allow(clippy::...)], stop and ask: "Am I suppressing a real design problem?" If yes, fix the design. If the lint is genuinely a false positive for this specific context, suppress with a // ALLOW: comment explaining the rationale.

  4. cargo fmt is non-negotiable — all code must be formatted

  5. Recommended project-level Clippy configuration:

    For the standard [lints.clippy] and [lints.rust] blocks (single-crate and workspace variants), and the version pinning policy, see references/recommended-dependencies.md §Workspace Lint Configuration and §Starter Cargo.toml Template. Do not duplicate those blocks here — treat recommended-dependencies.md as the single source of truth.

  6. Document all public items:

    • Every pub fn, pub struct, pub enum, pub trait, and pub type MUST have a /// doc comment
    • At minimum: one-line summary. For complex items: summary + parameters + errors + examples
    • Enable the missing_docs lint in library crates:
    toml
    # In Cargo.toml
    [lints.rust]
    missing_docs = "warn"
    rust
    // ❌ Undocumented public item
    pub fn resolve_symbols(path: &Path) -> Result<Vec<Symbol>> { ... }
    
    // ✅ Documented
    /// Resolves all exported symbols from the file at `path`.
    ///
    /// Returns parsed symbol definitions including their span information.
    ///
    /// # Errors
    /// Returns `ParseError` if the file cannot be parsed by tree-sitter.
    pub fn resolve_symbols(path: &Path) -> Result<Vec<Symbol>> { ... }
Dependency Management
  1. Minimize dependency count — each dependency is an attack surface and compile-time cost
  2. Pin major versions in Cargo.toml — use dep = "1" not dep = "*"
  3. Audit regularly — run cargo audit to check for known vulnerabilities
  4. Prefer well-maintained crates — check download count, last commit date, and issue tracker
Cargo Features
  1. Features must be additive — enabling a feature must only add functionality, never change or remove existing behavior

  2. Use dep: syntax for optional dependencies to keep the feature namespace clean:

    toml
    [features]
    default = ["json"]
    json = ["dep:serde_json"]    # ✅ Uses dep: prefix
    grpc = ["dep:tonic"]         # ✅ Feature doesn't auto-expose dep as feature
  3. Guard feature-gated code with #[cfg(feature = "...")]:

    rust
    #[cfg(feature = "grpc")]
    pub mod grpc_handler;
  4. Test feature combinations in CI using cargo-hack:

    bash
    cargo hack test --feature-powerset --depth 2
  5. Never use features for mutually exclusive backends — use traits and runtime selection instead

Configuration and Environment
  1. Never use string literals directly in std::env::var():

    • Define all environment variable names as constants in a central module
    • This prevents typos (caught at compile time) and enables grep-ability
    rust
    // ❌ Bug-prone — typos are silent, scattered across codebase
    let port = std::env::var("PATHFINDER_PORT").unwrap_or("3000".into());
    let host = std::env::var("PATHFNDER_HOST").unwrap_or("localhost".into()); // typo!
    
    // ✅ Safe — constants catch typos at compile time, single source of truth
    mod env_keys {
        pub const PORT: &str = "PATHFINDER_PORT";
        pub const HOST: &str = "PATHFINDER_HOST";
    }
    let port = std::env::var(env_keys::PORT).unwrap_or_else(|_| "3000".into());
    let host = std::env::var(env_keys::HOST).unwrap_or_else(|_| "localhost".into());
  2. Prefer structured config parsing over scattered env::var calls:

    • Parse all config at startup into a typed struct
    • Validate required values fail-fast at boot, not at first use
Safety, Security, and Performance

Key safety rules (non-negotiable):

  • Never use unsafe without a // SAFETY: comment documenting the invariant
  • Never transmute across types of different sizes or with different validity invariants
  • Validate all as casts with explicit bounds checks — as silently truncates
  • Never block the async runtime — use tokio::task::spawn_blocking for CPU-heavy or synchronous I/O work inside async contexts

For the full catalog of safety invariants, SAST patterns, concurrency rules (lock guards, atomics, async), memory safety (double indirection, transmute, pointer casts), collection best practices (retain, deterministic iteration), and security (TOCTOU, path traversal, cookie flags, CSP), see references/rust-patterns-and-anti-patterns.md. Load it before writing any unsafe code, concurrent code, or I/O handling code.

For performance patterns (arena allocation, SmallVec, zero-copy parsing, Cow, pre-sized collections, benchmarking), see perf-optimization/languages/rust.md.

  • Error Handling Principles @.agents/rules/error-handling-principles.md
  • Security Principles @.agents/rules/security-principles.md
  • Architectural Patterns — Testability-First Design @.agents/rules/architectural-pattern.md
  • Concurrency and Threading Principles @.agents/rules/concurrency-and-threading-principles.md
  • Core Design Principles @.agents/rules/core-design-principles.md
  • Performance Optimization Principles @.agents/rules/performance-optimization-principles.md
  • Resource and Memory Management Principles @.agents/rules/resources-and-memory-management-principles.md
  • Security Mandate @.agents/rules/security-mandate.md
  • Code Idioms and Conventions @.agents/rules/code-idioms-and-conventions.md
  • Testing Strategy @.agents/rules/testing-strategy.md
  • Logging and Observability Mandate @.agents/rules/logging-and-observability-mandate.md
  • Dependency Management Principles @.agents/rules/dependency-management-principles.md
  • Logging Implementation @.agents/skills/logging-implementation/SKILL.md
  • Axum Idioms @.agents/skills/axum-idioms/SKILL.md
  • Testability Patterns @.agents/skills/testability-patterns/SKILL.md
  • Performance (Rust) @.agents/skills/perf-optimization/languages/rust.md

© irahardianto, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

SKILL.md and 5 other files (references) in .agents/skills/rust-idioms of irahardianto/awesome-agv.

  • SKILL.md
  • references/project-structure.md
  • references/recommended-dependencies.md
  • references/rust-patterns-and-anti-patterns.md
  • references/serde-patterns.md
  • references/sqlx-patterns.md

Open the folder on GitHubat commit 9e997ba

Compare with similar skills

Rust Idioms next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

Rust Idioms compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Rust Idioms this skillirahardianto/awesome-agv156—~6.8kAutomated safety check: PassMIT
Migrate Core Code to Submodulestinyhumansai/openhuman42k—~2.6kAutomated safety check: PassGPL-3.0
OpenLogi macOS Permissions TriageAprilNEA/OpenLogi23k—~2.5kAutomated safety check: NotesApache-2.0
Rust Best Practicesfarm-fe/farm5.6k3 repos~1.1kAutomated safety check: PassMIT
RTK Rust Design Patternsrtk-ai/rtk83k—~1.9kAutomated safety check: PassApache-2.0
Release Skillsnexmoe/eve4213 repos~3.3kAutomated safety check: PassNone

Similar skills

  • Migrate Core Code to Submodules

    tinyhumansai/openhuman

    Plans and carries out moving non-host-specific code and its tests from the OpenHuman core into vendored tiny submodule libraries, then releases the submodule and re-pins the host.

    42k GitHub stars~2.6k tokensUpdated today
    DevelopmentAuto-check passed
  • Decides whether an OpenLogi device problem on macOS is a privacy-permission (TCC) problem, using agent log lines, and says which identity needs which grant.

    23k GitHub stars~2.5k tokensUpdated today
    DevelopmentAuto-check: notes
  • Guide for writing idiomatic Rust code based on Apollo GraphQL's best practices handbook.

    5.6k GitHub starsUsed in 3 repos~1.1k tokens
    DevelopmentAuto-check passed
  • Describes seven Rust design patterns for the RTK CLI filter modules, with when to use each, RTK examples, and notes on when a pattern is overkill.

    83k GitHub stars~1.9k tokensUpdated today
    DevelopmentAuto-check passed
  • Release Skills

    nexmoe/eve

    Universal release workflow. An agent skill from nexmoe/eve.

    421 GitHub starsUsed in 3 repos~3.3k tokens
    DevelopmentAuto-check passed
  • Pnpm Engine

    teambit/bit

    Work on the pnpm Rust engine (@pnpm/napi, the pacquet crates) that bit install runs through.

    18k GitHub stars~1.9k tokensUpdated today
    DevelopmentAuto-check passed

More from irahardianto/awesome-agv

All 34 skills in this repo
  • Distinctive Frontend Design Builder

    irahardianto/awesome-agv

    Commits to one bold aesthetic direction, sets up a CSS token system for it, then builds the interface in Vue or plain HTML using those tokens.

    156 GitHub stars~2.4k tokensUpdated 5 days ago
    Auto-check passed
  • Perf Optimization

    irahardianto/awesome-agv

    Profile-driven performance optimization protocol. An agent skill from irahardianto/awesome-agv.

    156 GitHub stars~4.3k tokensUpdated 5 days ago
    Auto-check passed
  • Angular Idioms and Patterns

    irahardianto/awesome-agv

    Coding conventions for Angular 19 and later: standalone components, signals, OnPush change detection, lazy routes and where RxJS still belongs.

    156 GitHub stars~3.8k tokensUpdated 5 days ago
    Auto-check passed
  • CI/CD Pipeline Principles

    irahardianto/awesome-agv

    Rules for designing CI/CD pipelines in layers: universal lint, test and scan stages, container builds with SBOM attestation, and GitOps for orchestrated deployments.

    156 GitHub stars~2.7k tokensUpdated 5 days ago
    Auto-check: notes
  • Hono Idioms

    irahardianto/awesome-agv

    Hono lightweight web framework patterns: type-safe route handlers, middleware composition, Zod validation, and RPC clients for Cloudflare Workers, Node, or Bun.

    156 GitHub stars~3k tokensUpdated 5 days ago
    Auto-check passed
  • Mobile Testing

    irahardianto/awesome-agv

    Mobile E2E testing patterns — Flutter integrationtest, Patrol, Maestro, golden testing, device matrix, and test data management.

    156 GitHub stars~1.8k tokensUpdated 5 days ago
    Auto-check: notes

Works with

Categories

Questions about Rust Idioms

What does Rust Idioms do?

Rust idioms: ownership and borrow checker patterns, error handling with thiserror/anyhow, Tokio async concurrency, lifetime management, and Clippy pedantic compliance. Rust Idioms is an agent skill from irahardianto/awesome-agv. Rust idioms: ownership and borrow checker patterns, error handling with thiserror/anyhow, Tokio async concurrency, lifetime management, and Clippy pedantic compliance.

When should I use Rust Idioms?

Rust Idioms fits situations like: reviewing Rust crates.

How do I install Rust Idioms in Claude Code?

Run `npx skills add irahardianto/awesome-agv --skill rust-idioms -a claude-code`. Or copy the skill folder (.agents/skills/rust-idioms in irahardianto/awesome-agv) into .claude/skills/rust-idioms in your project. Claude Code loads it when a task matches its description.

How do I install Rust Idioms in Codex?

Run `npx skills add irahardianto/awesome-agv --skill rust-idioms -a codex`. Or copy the skill folder (.agents/skills/rust-idioms in irahardianto/awesome-agv) into .agents/skills/rust-idioms in your project. Codex loads it when a task matches its description.

Can I use Rust Idioms in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add irahardianto/awesome-agv --skill rust-idioms -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/rust-idioms, .gemini/skills/rust-idioms, .github/skills/rust-idioms and .opencode/skills/rust-idioms in your project.

What does Rust Idioms need to run?

Going by SKILL.md and its folder, Rust Idioms needs the command-line tools its instructions call (cargo).

Does Rust Idioms access the network?

SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.

Is Rust Idioms safe to install?

Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.

What licence does Rust Idioms use?

Rust Idioms is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Rust Idioms use?

About 6.8k tokens (SKILL.md is roughly 27k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full. Its references folder adds about 16k tokens, read only when the agent opens those files.

What are the alternatives to Rust Idioms?

Skills that share tags, products or a category with Rust Idioms: Migrate Core Code to Submodules (tinyhumansai/openhuman, 42k stars), OpenLogi macOS Permissions Triage (AprilNEA/OpenLogi, 23k stars), Rust Best Practices (farm-fe/farm, 5.6k stars) and RTK Rust Design Patterns (rtk-ai/rtk, 83k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Rust Idioms?

irahardianto (a GitHub user) maintains it in irahardianto/awesome-agv, which has 156 GitHub stars. The repository holds 34 skills in this directory. The repository was last updated on October 5, 2026.

Source: irahardianto/awesome-agv on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.