Skip to content

/howto-code-in-rust

Use when writing, reviewing, or modifying Rust code - covers error handling with thiserror+miette, type system patterns, async and serde conventions, testing crates, dependency pinning, and module organization

shell
$ npx -y skills add ed3dai/ed3d-plugins --skill howto-code-in-rust --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.
  • You can call itInvoke it directly when you want it.
  • Slash command/howto-code-in-rust
How auto-invocation works

Context preview

The summary Claude sees to decide when to auto-load this skill.

Use when writing, reviewing, or modifying Rust code - covers error handling with thiserror+miette, type system patterns, async and serde conventions, testing crates, dependency pinning, and module organization

SKILL.md

howto-code-in-rust.SKILL.md
name: howto-code-in-rust
description: Use when writing, reviewing, or modifying Rust code - covers error handling with thiserror+miette, type system patterns, async and serde conventions, testing crates, dependency pinning, and module organization

Writing Rust

Overview

Rust house style. Applies whenever writing, reviewing, or modifying Rust code.

The two governing values: correctness over convenience, and pragmatic incrementalism. Use the type system aggressively to make invalid states unrepresentable, then evolve the design as patterns repeat rather than building speculative abstractions.

Correctness over convenience

  • Model the full error space. No shortcuts or simplified error handling.
  • Handle all edge cases: race conditions, signal timing, platform differences.
  • Use the type system to encode correctness constraints (newtypes, exhaustive matching, `#[must_use]`).
  • Prefer compile-time guarantees over runtime checks where possible.
  • When uncertain, explore and iterate rather than assume.

User-facing error quality

Standard pairing: `thiserror` for structured error enums, `miette` for user-facing diagnostics with source spans, help text, and related errors. They are complementary -- `thiserror` defines the shape, `miette` adds the diagnostic layer on top.

#[derive(Debug, thiserror::Error, miette::Diagnostic)]
enum ConfigError {
    #[error("failed to parse config at {path}")]
    #[diagnostic(help("check that the file is valid TOML"))]
    Parse { path: String, #[source] source: toml::de::Error },
}

Rules:

  • Group errors by category with an `ErrorKind` enum when a single error type covers many failure modes.
  • Two-tier error model: user-facing errors get semantic exit codes and rich diagnostics; internal errors (programming bugs) may panic or use internal error types.
  • Error display messages are lowercase sentence fragments suitable for composing as "failed to {message}".
  • Cross-platform consistency. Use OS-native logic rather than emulating Unix on Windows or vice versa.
  • User-facing messages in clear, present tense.
  • **Never silently drop unsupported content.** When translating or adapting data between formats, return `Err` for content the target format cannot represent. Silent data loss is a correctness bug. If a caller wants to ignore unsupported content, they can explicitly choose to -- but the library must surface the problem.

Type system patterns

Encode invariants in types:

  • **Newtypes** for domain types. Wrap primitive types to prevent misuse: `struct UserId(u64)` instead of bare `u64`.
  • **Builder patterns** for complex construction with many optional parameters.
  • **Type states** encoded in generics when state transitions matter and invalid states should be unrepresentable.
  • **Lifetimes** to avoid unnecessary cloning. Prefer borrows when data has a natural tree structure.
  • **Restricted visibility.** Use `pub(crate)` and `pub(super)` liberally. Default to the narrowest visibility that works.
  • **`#[non_exhaustive]`** on public types in library crates that have stable APIs. Allows adding variants or fields without a breaking change. Internal crates do not need it.

For concurrent code, use message passing or the actor model to avoid data races rather than shared mutable state behind locks.

Pragmatic incrementalism

  • Prefer specific, composable logic over abstract frameworks. Do not be overly generic.
  • Document non-obvious design decisions and trade-offs in code or commit messages.
  • Do not build for hypothetical future requirements. Rule of three: do not abstract until you have seen the pattern three times.

Research before guessing

When you encounter an unfamiliar crate, an unclear API, or a build problem you cannot immediately diagnose, use research agents rather than iterating by trial and error. Speculative iteration wastes build cycles and context.

  • `ed3d-research-agents:internet-researcher` for crate documentation, API behavior, and ecosystem conventions.
  • `ed3d-research-agents:remote-code-researcher` for examining external repositories for patterns and reference implementations.

These run in isolated context and return summaries, so they do not pollute working context.

Testing

  • Test comprehensively, including edge cases, race conditions, and platform differences.
  • Reuse existing test facilities. Before writing new test helpers, check whether the codebase already has what you need.
  • Unit tests belong in the same file as the code they test, inside a `#[cfg(test)] mod tests` block.
  • Integration tests and fixtures go in `tests/` at the crate root, not mixed with production sources.

Never skip tests

Tests must never silently skip. If a test requires an environment variable, API key, fixture file, or any other external input, it must **fail with a clear error message** when that input is unavailable. Never use patterns like `let Some(...) = ... else { return }` or `#[ignore]` or early-return guards that turn a missing dependency into a silent pass. A green test suite must mean every test actually ran and verified something. If a test cannot run, it must be red, not invisible.

Preferred testing crates

| Crate | Purpose | |-------|---------| | `test-case` | Parameterized tests. Annotate a single function with multiple input/output cases. | | `proptest` | Property-based testing. Generates random inputs to find edge cases you would not write by hand. | | `insta` | Snapshot testing. Captures complex output and diffs against stored snapshots. | | `pretty_assertions` | Better assertion output. Colored diffs instead of raw `Debug` output on failure. |

Serde patterns

  • Use `serde_ignored` to detect unused or typo'd fields in configuration deserialization.
  • Never use `#[serde(flatten)]`. The internal buffering breaks `serde_ignored` warnings, silently swallowing typos in config files.
  • Never use `#[serde(untagged)]` for deserializers. It produces useless error messages like "data did not m
Read more
Read it on GitHub ↗

Showing the first part of this file.

Ships withed3d-plugins

This is my collection of plugins that I use on a day-to-day basis for getting stuff done with Claude Code. Most of these are development-oriented in some way or another, but also often end up being useful for other things.

Get the whole plugin, auto-invoked