원클릭으로
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
// 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
| 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 |
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.
#[must_use]).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:
ErrorKind enum when a single error type covers many failure modes.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.Encode invariants in types:
struct UserId(u64) instead of bare u64.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.
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.
#[cfg(test)] mod tests block.tests/ at the crate root, not mixed with production sources.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.
| 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_ignored to detect unused or typo'd fields in configuration deserialization.#[serde(flatten)]. The internal buffering breaks serde_ignored warnings, silently swallowing typos in config files.#[serde(untagged)] for deserializers. It produces useless error messages like "data did not match any variant." Write custom visitors with an appropriate expecting method instead.When modifying any struct that is serialized to disk or over the wire, trace the full version matrix:
| Scenario | Question |
|---|---|
| Old reader + new data | Can it deserialize? Does it lose information? |
| New reader + old data | Does #[serde(default)] produce correct values? |
| Old writer + new data | Can it round-trip without data loss? |
The third case is easy to miss. #[serde(default)] allows old readers to deserialize new data, but old writers will still drop unknown fields on write-back, silently corrupting data.
Bump format versions proactively. If adding a field that will be semantically important, bump the version when adding the field, not when first using non-default values. This prevents older versions from silently corrupting data on write-back.
Library crates must never read environment variables. All configuration -- API keys, base URLs, auth modes, feature flags -- must be accepted as parameters (in structs, function arguments, or builder methods). Environment variable reading belongs exclusively to application entry points (main.rs, CLI argument parsing, or dedicated configuration loaders). This keeps libraries testable without environment manipulation and prevents hidden coupling to deployment details.
Be selective with async. Use it for I/O and concurrency; keep all other code synchronous. Async infecting non-I/O code makes testing harder and adds complexity for no benefit.
async fn in traits directly. The async_trait macro is no longer needed on Rust 1.85+.tokio::task::JoinSet for concurrent task groups that need to be awaited together.mpsc channels. Unbounded channels hide backpressure problems.block_on at application entry points. Do not try to make a single library support both sync and async APIs.clippy::format_push_string. It catches push_str(&format!(...)) which allocates unnecessarily. Use the write! macro instead.#[expect(...)] instead of #[allow(...)] for suppressing lints. expect warns when the suppression is no longer needed, preventing stale suppressions from accumulating.Arc or borrows for shared immutable data. Avoid cloning when code has a natural tree structure.mod.rs files for re-exports only. Put all nontrivial logic in imp.rs or a more specific submodule.unix.rs, windows.rs. Use #[cfg(unix)] and #[cfg(windows)] for conditional compilation.cfg()-gated imports, which may appear inline.use std::fmt and then fmt::Display, not std::fmt::Display.//!) should explain purpose and responsibilities.Pinning strategy depends on what you are building.
Applications, binaries, and internal/workspace crates: pin exactly. Cargo's default caret behavior (serde = "1.0" resolves to anything >=1.0.0, <2.0.0) lets dependency resolution shift between cargo update runs. For reproducibility and reviewable bumps, pin exactly:
[dependencies]
serde = "=1.0.219"
tokio = { version = "=1.43.0", features = ["full"] }
Libraries published to crates.io: use the narrowest range that works. Exact pins in a published library break diamond-dependency unification for downstream consumers -- if crate-a pins =1.0.219 and crate-b pins =1.0.220, a consumer depending on both gets two copies of the dependency or a hard resolution failure. Publish caret ranges (or tighter, e.g., >=1.0.219, <1.1) and let downstream Cargo.lock files do the pinning.
Other dependency rules:
ed3d-research-agents:internet-researcher to look up the current version on crates.io. Memorized versions go stale quickly.# disable punycode parsing since we only access well-known domains.Cargo.toml [workspace.dependencies] table and reference with { workspace = true } in member crates.ALWAYS use this skill when writing or refactoring code. Includes context-dependent sub-skills to empower different coding styles across languages and runtimes.
Use when the user wants to export a Claude Code session transcript as a readable Markdown file — converts the current session (or a specified transcript path) into GitHub-flavored Markdown with metadata header, collapsible tool results, and thinking blocks
Use when the user wants to review their recent Claude Code sessions for patterns — analyzes the last N sessions (default 5) in the current project, dispatching parallel reviewers per session, then synthesizing cross-session findings
Use when the user wants to review a Claude Code session for quality — analyzes the current session (or a specified transcript path) for prompting effectiveness, agent performance, and environment gaps, producing actionable recommendations
Use when writing skills, CLAUDE.md files, agent prompts, or any directives that involve shell commands, environment variables, API credentials, file creation, or git operations - prevents secrets leakage into LLM context, unsafe shell patterns, and credential exposure
Use when writing instructions that guide Claude behavior - skills, CLAUDE.md files, agent prompts, system prompts. Covers token efficiency, compliance techniques, and discovery optimization.