Skip to content
Back to skills

Rust Idioms

ASecurity

Idiomatic, production Rust for Vorn's crates (types, errors, memory, async, testing, performance, tooling, dependencies). Use while writing or reviewing any Rust in the repo, and as the checklist for re-reading your own diff before a push.

  • 26 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 6, 2026
ai-agentstypescriptrustgoshellnodetestinggitapiperformance

Works with

  • cursor
  • terminal
  • cli
  • api

Security analysis

A100/100

Scanned October 6, 2026

npx -y skills add vorn-run/vorn --skill rust-idioms --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Rust Idioms?

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

Security grade badge for Rust Idioms
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/vorn-run-rust-idioms/badge)](https://www.skillsdirectory.com/skills/vorn-run-rust-idioms)

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-idioms
description: Idiomatic, production Rust for Vorn's crates (types, errors, memory, async, testing, performance, tooling, dependencies). Use while writing or reviewing any Rust in the repo, and as the checklist for re-reading your own diff before a push.
---

# Rust idioms for Vorn

Written for the pinned toolchain (`packages/core/rust-toolchain.toml`) and for
a terminal core where every byte a shell prints passes through this
code. When a rule here and the surrounding code disagree, match the surrounding
code and raise it in the PR.

## Types and data

- **Model states as enums**, not flags plus `Option`s. If two fields are only
  valid together, they belong in one variant.
- **Newtypes for units that must not mix**: byte offsets, record numbers,
  epochs, UTF-16 lengths, columns. `struct Rseq(u64)` costs nothing and makes a
  swapped argument a compile error.
- **Strings**: take `&str` (or `&[u8]` for terminal bytes, which are not
  UTF-8 guaranteed), return `String` only when the caller needs ownership.
  `Cow<'_, str>` when a function usually returns its input unchanged.
- **Collections**: `Vec` by default; `VecDeque` for rings; `HashMap` for
  lookups; `BTreeMap` when order matters or for deterministic output in tests
  and serialization. Derive `Hash, Eq, PartialEq` together, never by hand
  unless the semantics differ.
- **Conversions** through `From`/`TryFrom`, not ad hoc `to_x()` helpers. Use
  `TryFrom` whenever the conversion can lose information (`usize` to `u32` at
  the napi boundary is the usual one).
- Derive `Debug` on every public type; derive `Clone`, `Default`, `Copy` only
  when they are cheap and meaningful.

## Errors

- Library crates return `Result<T, CrateError>` with an error enum per crate
  (implement `std::error::Error` and `Display`, by hand or with `thiserror` if
  it is already in the tree). The message says what failed and with which
  input, in the words the TypeScript used when parity matters.
- `?` to propagate, with `From` impls between layers. No `Box<dyn Error>` in a
  library's public API.
- `unwrap()`/`expect()` only for invariants the code itself establishes, with
  `expect("why this cannot fail")`. Never on input from a PTY, a file, the
  network or JS.
- Panics never cross FFI; see the `napi-boundary` skill.

## Ownership and memory

- Prefer moving and borrowing to cloning. A `.clone()` on the hot path needs a
  reason; on a `String` or `Vec` per chunk it is a bug.
- Choose the smallest tool: `Box` for one heap value, `Rc`/`RefCell` only on a
  single thread, `Arc` to share across threads, `Arc<Mutex<_>>` or
  `Arc<RwLock<_>>` for shared mutation. Before adding a lock, try ownership by
  one thread plus a channel.
- Globals: `std::sync::LazyLock` / `OnceLock` (not `lazy_static`). No
  `static mut`.
- Preallocate when the size is known (`Vec::with_capacity`,
  `String::with_capacity`, `reserve`), and reuse buffers across calls instead
  of allocating per chunk. `extend_from_slice` / `copy_from_slice` for byte
  copies.
- `unsafe` only at an FFI edge, wrapped in a safe function, with a
  `// SAFETY:` comment stating the invariant. `#[repr(C)]` and `std::ffi`
  types for anything crossing a C boundary.

## Async and threads

- Async only where there is real concurrency (I/O, many sessions). A
  sequential computation is a plain function; call it from async code with
  `tokio::task::spawn_blocking`.
- Never block inside an async task: no `std::fs`, `std::process`, long loops
  or `Mutex` held across `.await`. Use `tokio::sync` primitives when a lock
  must span an await.
- Message passing over shared state: `tokio::sync::mpsc` (bounded, so a
  slow consumer applies backpressure), `oneshot` for replies, `broadcast` or
  `watch` for fan-out.
- Bound concurrency explicitly (a `Semaphore`, as `src/git.rs` does) rather
  than spawning per request.
- `tokio::select!` for cancellation and timeouts; every loop that waits has a
  way out.
- Avoid `block_on` inside a runtime. Bridging sync to async is a design smell
  here.
- Instrument long-lived tasks with `tracing` spans once a daemon exists.

## Testing

- Do not test what the type system proves. Test logic, boundaries and
  invariants: empty input, a chunk split mid escape sequence, mid UTF-8
  codepoint, a wide character at the last column, a record at an epoch change,
  a cursor exactly at a boundary.
- Unit tests next to the code; integration tests in `tests/` use only the
  public API, which also tests that the API is pleasant to use.
- Tests run in parallel. No shared mutable globals; give each test its own
  temp dir (`tempfile`) and its own instance.
- Property tests for round trips (serialize then restore, write then read,
  feed in one chunk versus many). Seed them and keep failing cases as
  fixtures.
- Fuzz (`cargo fuzz`) parsers of untrusted bytes when the spec calls for it;
  never let a fuzz input panic.
- `#[tokio::test]` for async code; prefer paused time (`start_paused = true`)
  to real sleeps.
- A test that needs something absent (the Ghostty build, git) skips with a
  clear reason; it never silently passes.

## Performance

- Measure in release (`--release`); debug numbers mean nothing. Benchmarks are
  Criterion benches in the plain crate, plus `yarn bench` for the end-to-end
  JS baseline.
- Do one pass over the bytes. `memchr` for scanning, slices instead of
  substrings, iterators over index loops (they elide bounds checks).
- Batch work across the FFI boundary; a call per chunk costs more than the
  work in it.
- Parallelize with `rayon` only for independent CPU-bound work on large
  inputs; the terminal path is usually latency-bound, not throughput-bound.
- No portable SIMD or nightly features; the toolchain is stable and pinned.

## Tooling and hygiene

- `cargo fmt` and `cargo clippy --all-targets -- -D warnings` must be clean.
  Fix lints; `#[allow(clippy::...)]` only with a comment saying why, on the
  smallest item.
- `//!` doc on every module and `///` on every public item, saying why as well
  as what. Doc examples compile; use them for small API examples.
- After adding a dependency locally, `cargo tree -p <crate>` shows what it
  pulls in; check that before committing. `cargo tree -i <crate>` shows who
  depends on a crate. `cargo expand` debugs a macro (napi-derive included).
- Dependencies: prefer the standard library and crates already in
  `Cargo.lock`. Turn off default features and enable only what is used.
  Optional heavy deps sit behind a cargo feature (as `ghostty` does).
- Patch or fork a dependency only as a last resort, upstream the fix, and get
  back on the released version quickly.
- `Cargo.lock` is committed; CI builds with `--locked`.

## Before you push: re-read the diff

1. Any `unwrap`, `expect`, `panic!`, indexing or `as` cast on outside input?
2. Any allocation or clone per chunk that could be a reused buffer?
3. Any blocking call reachable from an async task or from Node's thread?
4. Any `unsafe` without a `SAFETY` comment, or exposed without a safe wrapper?
5. Do new public items have docs, and do error messages say what failed?
6. Is every new test checking logic, and does each must-pass id have one?

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…