Rust
Purpose
Write Rust that satisfies the borrow checker by design rather than by fighting it. Ownership is a modeling decision made before the code is written, not a compiler obstacle discovered afterward.
When to Use
- Building Rust libraries, services, or CLI tools.
- Resolving lifetime, borrow, or trait-bound errors.
- Designing error types for a library versus an application.
- Writing async Rust with Tokio.
- Auditing or minimizing
unsafe blocks.
Capabilities
- Ownership and borrowing design: when to clone, when to borrow, when to use
Rc/Arc.
- Error modeling:
thiserror for libraries, anyhow for binaries.
- Trait design, generic bounds, and trait objects.
- Async runtimes,
Send/Sync bounds, and cancellation semantics.
- Safe encapsulation of
unsafe, with documented invariants.
Inputs
- Crate or module source.
- Edition and MSRV.
- Whether the artifact is a library (stable public API) or a binary.
Outputs
- Code that compiles with zero warnings under
clippy::pedantic (or a documented allow-list).
- Explicit error enums with
#[from] conversions.
- Tests including compile-fail tests for API misuse where warranted.
Workflow
- Model ownership first — Decide who owns each value and how long it lives before writing the signature.
- Design the error type — A library's error enum is public API. Enumerate failure modes explicitly.
- Implement — Start with owned values and clones. Optimize to borrows only where profiling or ergonomics justify it.
- Constrain generics late — Write it concrete, then generalize if a second caller appears.
- Gate —
cargo clippy -- -D warnings, cargo fmt --check, cargo test, cargo deny check.
Best Practices
- A
clone() in non-hot-path code is not a defect. Premature borrow optimization produces unreadable lifetimes.
- Libraries return concrete error enums; binaries use
anyhow::Result and add context at each layer.
- Never
unwrap() outside tests and main. Use expect("invariant: ...") where the invariant is genuinely proven.
- Prefer
impl Trait in argument position for simple bounds; use named generics when the caller must choose the type.
- Every
unsafe block gets a // SAFETY: comment stating the invariant that makes it sound.
- Use
#[non_exhaustive] on public enums you may extend.
Examples
Library error type:
use thiserror::Error;
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum StoreError {
#[error("key not found: {0}")]
NotFound(String),
#[error("storage backend unavailable")]
Unavailable(#[source] std::io::Error),
#[error("value for {key} exceeds {limit} bytes")]
TooLarge { key: String, limit: usize },
}
pub fn get(key: &str) -> Result<Vec<u8>, StoreError> {
std::fs::read(key).map_err(|e| match e.kind() {
std::io::ErrorKind::NotFound => StoreError::NotFound(key.to_owned()),
_ => StoreError::Unavailable(e),
})
}
Async with timeout and cancellation:
use tokio::time::{timeout, Duration};
pub async fn fetch_with_deadline(url: &str) -> anyhow::Result<String> {
let body = timeout(Duration::from_secs(5), reqwest::get(url))
.await
.context("request timed out after 5s")?
.context("request failed")?
.text()
.await?;
Ok(body)
}
Notes
- Borrow-checker errors are usually design feedback. If a lifetime is hard to name, the ownership model is probably wrong.
Arc<Mutex<T>> is a legitimate answer, not a failure. Contention, not the type, is what costs you.
- Async traits are stable as of Rust 1.75 for non-object-safe use;
async-trait is still needed for trait objects.
1---2name: rust3description: Use when writing Rust or resolving borrow-checker, lifetime, and trait errors. Covers ownership models, error handling with thiserror and anyhow, async with Tokio, and safe abstractions over unsafe code.4---56# Rust78## Purpose910Write Rust that satisfies the borrow checker by design rather than by fighting it. Ownership is a modeling decision made before the code is written, not a compiler obstacle discovered afterward.1112## When to Use1314- Building Rust libraries, services, or CLI tools.15- Resolving lifetime, borrow, or trait-bound errors.16- Designing error types for a library versus an application.17- Writing async Rust with Tokio.18- Auditing or minimizing `unsafe` blocks.1920## Capabilities2122- Ownership and borrowing design: when to clone, when to borrow, when to use `Rc`/`Arc`.23- Error modeling: `thiserror` for libraries, `anyhow` for binaries.24- Trait design, generic bounds, and trait objects.25- Async runtimes, `Send`/`Sync` bounds, and cancellation semantics.26- Safe encapsulation of `unsafe`, with documented invariants.2728## Inputs2930- Crate or module source.31- Edition and MSRV.32- Whether the artifact is a library (stable public API) or a binary.3334## Outputs3536- Code that compiles with zero warnings under `clippy::pedantic` (or a documented allow-list).37- Explicit error enums with `#[from]` conversions.38- Tests including compile-fail tests for API misuse where warranted.3940## Workflow41421. **Model ownership first** — Decide who owns each value and how long it lives before writing the signature.432. **Design the error type** — A library's error enum is public API. Enumerate failure modes explicitly.443. **Implement** — Start with owned values and clones. Optimize to borrows only where profiling or ergonomics justify it.454. **Constrain generics late** — Write it concrete, then generalize if a second caller appears.465. **Gate** — `cargo clippy -- -D warnings`, `cargo fmt --check`, `cargo test`, `cargo deny check`.4748## Best Practices4950- A `clone()` in non-hot-path code is not a defect. Premature borrow optimization produces unreadable lifetimes.51- Libraries return concrete error enums; binaries use `anyhow::Result` and add context at each layer.52- Never `unwrap()` outside tests and `main`. Use `expect("invariant: ...")` where the invariant is genuinely proven.53- Prefer `impl Trait` in argument position for simple bounds; use named generics when the caller must choose the type.54- Every `unsafe` block gets a `// SAFETY:` comment stating the invariant that makes it sound.55- Use `#[non_exhaustive]` on public enums you may extend.5657## Examples5859**Library error type:**6061```rust62use thiserror::Error;6364#[derive(Debug, Error)]65#[non_exhaustive]66pub enum StoreError {67 #[error("key not found: {0}")]68 NotFound(String),6970 #[error("storage backend unavailable")]71 Unavailable(#[source] std::io::Error),7273 #[error("value for {key} exceeds {limit} bytes")]74 TooLarge { key: String, limit: usize },75}7677pub fn get(key: &str) -> Result<Vec<u8>, StoreError> {78 std::fs::read(key).map_err(|e| match e.kind() {79 std::io::ErrorKind::NotFound => StoreError::NotFound(key.to_owned()),80 _ => StoreError::Unavailable(e),81 })82}83```8485**Async with timeout and cancellation:**8687```rust88use tokio::time::{timeout, Duration};8990pub async fn fetch_with_deadline(url: &str) -> anyhow::Result<String> {91 let body = timeout(Duration::from_secs(5), reqwest::get(url))92 .await93 .context("request timed out after 5s")?94 .context("request failed")?95 .text()96 .await?;97 Ok(body)98}99```100101## Notes102103- Borrow-checker errors are usually design feedback. If a lifetime is hard to name, the ownership model is probably wrong.104- `Arc<Mutex<T>>` is a legitimate answer, not a failure. Contention, not the type, is what costs you.105- Async traits are stable as of Rust 1.75 for non-object-safe use; `async-trait` is still needed for trait objects.