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.
Scanned 9/9/2026
Install to Claude Code
npx -y skills add petekp/agent-skills --skill rust --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Rust?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/petekp-rust-agent-skills)More formats (shields.io, HTML) on the badges page.
---
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)
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!