home / skills / petekp / claude-code-setup / rust
This skill helps you apply robust Rust patterns for file I/O, parsing, and cross-language boundaries to build reliable systems.
npx playbooks add skill petekp/claude-code-setup --skill rustReview the files below or copy the command above to add this skill to your agents.
---
name: rust
description: Robust Rust patterns for file-backed data, parsing, persistence, FFI boundaries, and system integration. Use when writing Rust that handles file formats, subprocess integration, PID/process management, Serde serialization, or UniFFI boundaries. Covers UTF-8 safety, atomic writes, state machines, and defensive error handling.
---
# Rust Engineering Guide
Patterns for building reliable Rust systems that handle file-backed data, external process integration, and cross-language boundaries.
## Core Philosophy
**Conservative by Default**: Inputs from files, subprocesses, and external systems are untrusted.
- Prefer false negatives over false positives
- Same input → same output (deterministic)
- Never panic on user machines due to bad input
**Canonical Model Ownership**: When Rust is the source of truth, maintain separate representations:
| Layer | Purpose | Characteristics |
|-------|---------|-----------------|
| Internal domain | Business logic | Expressive enums, rich types |
| FFI DTOs | Cross-language boundary | Flat, stable, `String`-heavy |
| File format | Persistence | Versioned, round-trippable |
| External input | Validation boundary | Strictly validated, never trusted |
**Safe Rust Only**: None of these patterns require `unsafe`. Use ecosystem crates for safe abstractions.
---
## Reference Guides
Load the relevant reference when working in that domain:
| Domain | Reference | When to Load |
|--------|-----------|--------------|
| **Data Modeling** | [references/data-modeling.md](references/data-modeling.md) | Serde patterns, UniFFI, strong types, versioned schemas |
| **File I/O** | [references/file-io.md](references/file-io.md) | Atomic writes, concurrency control, file watching |
| **Process Integration** | [references/process-integration.md](references/process-integration.md) | PID verification, subprocess handling, timestamps |
| **Text & Parsing** | [references/text-and-parsing.md](references/text-and-parsing.md) | UTF-8 safety, path normalization, state machines |
| **Testing** | [references/testing.md](references/testing.md) | Round-trip tests, fuzz testing, Clippy lints |
---
## Error Handling
### Library vs Application Errors
**Libraries** (public API): Use `thiserror` with granular error types per operation:
```rust
// File operations have their own error type
#[derive(thiserror::Error, Debug)]
pub enum ReadError {
#[error("failed to read {path}")]
Io { path: PathBuf, #[source] source: std::io::Error },
#[error("parse error at line {line}: {message}")]
Parse { line: usize, message: String },
}
// Subprocess operations have their own error type
#[derive(thiserror::Error, Debug)]
pub enum SubprocessError {
#[error("failed to spawn process")]
Spawn(#[source] std::io::Error),
#[error("process exited with {code:?}: {stderr}")]
NonZeroExit { code: Option<i32>, stderr: String },
#[error("output not valid UTF-8")]
InvalidUtf8(#[source] std::str::Utf8Error),
#[error("timed out after {0:?}")]
Timeout(std::time::Duration),
}
```
**Applications** (internal/binary): Use `anyhow` for context-rich errors:
```rust
use anyhow::{Context, Result};
fn load_config(path: &Path) -> Result<Config> {
let content = std::fs::read_to_string(path)
.with_context(|| format!("failed to read config from {}", path.display()))?;
// ...
}
```
### Graceful Degradation
Errors degrade functionality, not crash. But *log* when being lenient:
```rust
match parse_metadata(&line) {
Ok(meta) => entries.push(meta),
Err(e) => {
tracing::warn!("skipping malformed entry at line {}: {}", line_num, e);
// Continue processing other entries
}
}
```
---
## Quick Reference
### Do
- Use `std::sync::LazyLock` for static regex (Rust 1.80+)
- Hold locks across entire read-modify-write cycles
- Add `#[serde(deny_unknown_fields)]` for strict external input
- Truncate strings with `.chars()` or graphemes, not byte slicing
- Write files atomically with `sync_all()` before rename
- Verify PID identity with process start time
- Use `saturating_sub` for time arithmetic
- Run `cargo clippy -- -D warnings` and `cargo fmt` before commit
### Don't
- Use `#[from]` without adding context (loses *which* file failed)
- Create monolithic error enums spanning unrelated operations
- Silently ignore errors without logging
- Slice strings with `&s[..N]` (panics on char boundaries)
- Assume directory iteration order is stable
- Trust subprocess output without validation
- Use `unsafe` (not needed for these patterns)
---
## Bugs This Guide Prevents
| Bug | Pattern | Reference |
|-----|---------|-----------|
| PID reuse "ghost sessions" | Store + verify process start time | [process-integration.md](references/process-integration.md) |
| Timestamp unit mismatch (sec vs ms) | Normalize on read | [process-integration.md](references/process-integration.md) |
| UTF-8 panic on truncation | Use `.chars().take(n)` | [text-and-parsing.md](references/text-and-parsing.md) |
| Lost updates under concurrency | Lock spans full read-modify-write | [file-io.md](references/file-io.md) |
| Corrupt file on power loss | `sync_all()` before rename | [file-io.md](references/file-io.md) |
| Silent metadata corruption | Anchor updates to heading lines | [text-and-parsing.md](references/text-and-parsing.md) |
| Old data breaks new code | `#[serde(default)]` + `alias` | [data-modeling.md](references/data-modeling.md) |
---
## Change Checklist
When modifying these systems, verify:
**Schema / Serde**
- [ ] New fields use `Option` + `#[serde(default)]`
- [ ] Old field names supported via `alias` (read) or `rename` (write)
- [ ] External input uses `#[serde(deny_unknown_fields)]`
**Concurrency**
- [ ] Mutex held across entire read-modify-write cycle
- [ ] Shared state uses `Mutex<T>`, not `thread_local!`
- [ ] File locking documents platform caveats if used
**Robustness**
- [ ] No panics on file I/O or parse errors
- [ ] Errors logged before being ignored
- [ ] Subprocesses have timeouts
**Quality**
- [ ] `cargo clippy -- -D warnings` passes
- [ ] `cargo fmt --check` passes
- [ ] No `unsafe` blocks (unless justified and audited)
This skill captures robust, production-ready Rust patterns for file-backed data, subprocess integration, FFI boundaries, and system-level concerns. It focuses on conservative validation, deterministic behavior, and defensive error handling to avoid panics and data corruption. Use it to design safe, maintainable Rust code that interacts with files, processes, or other languages.
The skill lays out layered ownership: expressive internal domain models, flat stable FFI DTOs, versioned file formats, and a strict external-input validation boundary. It prescribes safe Rust-only patterns—atomic writes with sync_all, PID verification with start times, Serde schema evolution, UTF-8-safe text handling, and clear, contextual error types for libraries and applications. It also recommends testing and linting workflows to keep behavior deterministic and auditable.
Should I use unsafe for FFI performance?
No. The patterns avoid unsafe; prefer stable crates and flat DTOs to keep cross-language boundaries safe.
When do I use anyhow vs thiserror?
Use thiserror for library-level, typed errors and anyhow in applications/binaries where adding context and flexible propagation is valuable.