chore: remove skills and docs dir. (#1631)

This commit is contained in:
安正超
2026-01-27 22:43:22 +08:00
committed by GitHub
parent 26c0230e8f
commit fff175dcdd
167 changed files with 2 additions and 31691 deletions

View File

@@ -1,94 +0,0 @@
---
name: coding-guidelines
description: "Use when asking about Rust code style or best practices. Keywords: naming, formatting, comment, clippy, rustfmt, lint, code style, best practice, P.NAM, G.FMT, code review, naming convention, variable naming, function naming, type naming, 命名规范, 代码风格, 格式化, 最佳实践, 代码审查, 怎么命名"
source: https://rust-coding-guidelines.github.io/rust-coding-guidelines-zh/
---
# Rust Coding Guidelines (50 Core Rules)
## Naming (Rust-Specific)
| Rule | Guideline |
|------|-----------|
| No `get_` prefix | `fn name()` not `fn get_name()` |
| Iterator convention | `iter()` / `iter_mut()` / `into_iter()` |
| Conversion naming | `as_` (cheap &), `to_` (expensive), `into_` (ownership) |
| Static var prefix | `G_CONFIG` for `static`, no prefix for `const` |
## Data Types
| Rule | Guideline |
|------|-----------|
| Use newtypes | `struct Email(String)` for domain semantics |
| Prefer slice patterns | `if let [first, .., last] = slice` |
| Pre-allocate | `Vec::with_capacity()`, `String::with_capacity()` |
| Avoid Vec abuse | Use arrays for fixed sizes |
## Strings
| Rule | Guideline |
|------|-----------|
| Prefer bytes | `s.bytes()` over `s.chars()` when ASCII |
| Use `Cow<str>` | When might modify borrowed data |
| Use `format!` | Over string concatenation with `+` |
| Avoid nested iteration | `contains()` on string is O(n*m) |
## Error Handling
| Rule | Guideline |
|------|-----------|
| Use `?` propagation | Not `try!()` macro |
| `expect()` over `unwrap()` | When value guaranteed |
| Assertions for invariants | `assert!` at function entry |
## Memory
| Rule | Guideline |
|------|-----------|
| Meaningful lifetimes | `'src`, `'ctx` not just `'a` |
| `try_borrow()` for RefCell | Avoid panic |
| Shadowing for transformation | `let x = x.parse()?` |
## Concurrency
| Rule | Guideline |
|------|-----------|
| Identify lock ordering | Prevent deadlocks |
| Atomics for primitives | Not Mutex for bool/usize |
| Choose memory order carefully | Relaxed/Acquire/Release/SeqCst |
## Async
| Rule | Guideline |
|------|-----------|
| Sync for CPU-bound | Async is for I/O |
| Don't hold locks across await | Use scoped guards |
## Macros
| Rule | Guideline |
|------|-----------|
| Avoid unless necessary | Prefer functions/generics |
| Follow Rust syntax | Macro input should look like Rust |
## Deprecated → Better
| Deprecated | Better | Since |
|------------|--------|-------|
| `lazy_static!` | `std::sync::OnceLock` | 1.70 |
| `once_cell::Lazy` | `std::sync::LazyLock` | 1.80 |
| `std::sync::mpsc` | `crossbeam::channel` | - |
| `std::sync::Mutex` | `parking_lot::Mutex` | - |
| `failure`/`error-chain` | `thiserror`/`anyhow` | - |
| `try!()` | `?` operator | 2018 |
## Quick Reference
```
Naming: snake_case (fn/var), CamelCase (type), SCREAMING_CASE (const)
Format: rustfmt (just use it)
Docs: /// for public items, //! for module docs
Lint: #![warn(clippy::all)]
```
Claude knows Rust conventions well. These are the non-obvious Rust-specific rules.

View File

@@ -1,16 +0,0 @@
# Clippy Lint → Rule Mapping
| Clippy Lint | Category | Fix |
|-------------|----------|-----|
| `unwrap_used` | Error | Use `?` or `expect()` |
| `needless_clone` | Perf | Use reference |
| `await_holding_lock` | Async | Scope guard before await |
| `linkedlist` | Perf | Use Vec/VecDeque |
| `wildcard_imports` | Style | Explicit imports |
| `missing_safety_doc` | Safety | Add `# Safety` doc |
| `undocumented_unsafe_blocks` | Safety | Add `// SAFETY:` |
| `transmute_ptr_to_ptr` | Safety | Use `pointer::cast()` |
| `large_stack_arrays` | Mem | Use Vec or Box |
| `too_many_arguments` | Design | Use struct params |
For unsafe-related lints → see `unsafe-checker` skill.

View File

@@ -1,6 +0,0 @@
# Complete Rules Reference
For the full 500+ rules, see:
- Source: https://rust-coding-guidelines.github.io/rust-coding-guidelines-zh/
Core rules are in `../SKILL.md`.

View File

@@ -1,49 +0,0 @@
---
name: core-actionbook
# Internal tool - no description to prevent auto-triggering
# Used by: rust-learner agents for pre-computed selectors
---
# Actionbook
Pre-computed action manuals for browser automation. Agents receive structured page information instead of parsing entire HTML.
## Workflow
1. **search_actions** - Search by keyword, returns URL-based action IDs with content previews
2. **get_action_by_id** - Get full action manual with page details, DOM structure, and element selectors
3. **Execute** - Use returned selectors with your browser automation tool
## MCP Tools
- `search_actions` - Search by keyword. Returns: URL-based action IDs, content previews, relevance scores
- `get_action_by_id` - Get full action details. Returns: action content, page element selectors (CSS/XPath), element types, allowed methods (click, type, extract), document metadata
### Parameters
**search_actions**:
- `query` (required): Search keyword (e.g., "airbnb search", "google login")
- `type`: `vector` | `fulltext` | `hybrid` (default)
- `limit`: Max results (default: 5)
- `sourceIds`: Filter by source IDs (comma-separated)
- `minScore`: Minimum relevance score (0-1)
**get_action_by_id**:
- `id` (required): URL-based action ID (e.g., `example.com/page`)
## Example Response
```json
{
"title": "Airbnb Search",
"url": "www.airbnb.com/search",
"elements": [
{
"name": "location_input",
"selector": "input[data-testid='structured-search-input-field-query']",
"type": "textbox",
"methods": ["type", "fill"]
}
]
}
```

View File

@@ -1,115 +0,0 @@
---
name: core-agent-browser
# Internal tool - no description to prevent auto-triggering
# Used by: rust-learner, docs-researcher, crate-researcher agents
---
# Browser Automation with agent-browser
## Priority Note
For fetching Rust/crate information, use this priority order:
1. **rust-learner skill** - Orchestrates actionbook + browser-fetcher
2. **actionbook MCP** - Pre-computed selectors for known sites
3. **agent-browser CLI** - Direct browser automation (last resort)
Use agent-browser directly only when:
- actionbook has no pre-computed selectors for the target site
- You need interactive browser testing/automation
- You need screenshots or form filling
## Quick start
```bash
agent-browser open <url> # Navigate to page
agent-browser snapshot -i # Get interactive elements with refs
agent-browser click @e1 # Click element by ref
agent-browser fill @e2 "text" # Fill input by ref
agent-browser close # Close browser
```
## Core workflow
1. Navigate: `agent-browser open <url>`
2. Snapshot: `agent-browser snapshot -i` (returns elements with refs like `@e1`, `@e2`)
3. Interact using refs from the snapshot
4. Re-snapshot after navigation or significant DOM changes
## Commands
### Navigation
```bash
agent-browser open <url> # Navigate to URL
agent-browser back # Go back
agent-browser forward # Go forward
agent-browser reload # Reload page
agent-browser close # Close browser
```
### Snapshot (page analysis)
```bash
agent-browser snapshot # Full accessibility tree
agent-browser snapshot -i # Interactive elements only (recommended)
agent-browser snapshot -c # Compact output
agent-browser snapshot -d 3 # Limit depth to 3
```
### Interactions (use @refs from snapshot)
```bash
agent-browser click @e1 # Click
agent-browser dblclick @e1 # Double-click
agent-browser fill @e2 "text" # Clear and type
agent-browser type @e2 "text" # Type without clearing
agent-browser press Enter # Press key
agent-browser press Control+a # Key combination
agent-browser hover @e1 # Hover
agent-browser check @e1 # Check checkbox
agent-browser uncheck @e1 # Uncheck checkbox
agent-browser select @e1 "value" # Select dropdown
agent-browser scroll down 500 # Scroll page
agent-browser scrollintoview @e1 # Scroll element into view
```
### Get information
```bash
agent-browser get text @e1 # Get element text
agent-browser get value @e1 # Get input value
agent-browser get title # Get page title
agent-browser get url # Get current URL
```
### Screenshots
```bash
agent-browser screenshot # Screenshot to stdout
agent-browser screenshot path.png # Save to file
agent-browser screenshot --full # Full page
```
### Wait
```bash
agent-browser wait @e1 # Wait for element
agent-browser wait 2000 # Wait milliseconds
agent-browser wait --text "Success" # Wait for text
agent-browser wait --load networkidle # Wait for network idle
```
### Semantic locators (alternative to refs)
```bash
agent-browser find role button click --name "Submit"
agent-browser find text "Sign In" click
agent-browser find label "Email" fill "user@test.com"
```
## Example: Form submission
```bash
agent-browser open https://example.com/form
agent-browser snapshot -i
# Output shows: textbox "Email" [ref=e1], textbox "Password" [ref=e2], button "Submit" [ref=e3]
agent-browser fill @e1 "user@example.com"
agent-browser fill @e2 "password123"
agent-browser click @e3
agent-browser wait --load networkidle
agent-browser snapshot -i # Check result
```

View File

@@ -1,87 +0,0 @@
---
name: core-dynamic-skills
# Command-based tool - no description to prevent auto-triggering
# Triggered by: /sync-crate-skills, /clean-crate-skills, /update-crate-skill
---
# Dynamic Skills Manager
Orchestrates on-demand generation of crate-specific skills based on project dependencies.
## Concept
Dynamic skills are:
- Generated locally at `~/.claude/skills/`
- Based on Cargo.toml dependencies
- Created using llms.txt from docs.rs
- Versioned and updatable
- Not committed to the rust-skills repository
## Trigger Scenarios
### Prompt-on-Open
When entering a directory with Cargo.toml:
1. Detect Cargo.toml (single or workspace)
2. Parse dependencies list
3. Check which crates are missing skills
4. If missing: "Found X dependencies without skills. Sync now?"
5. If confirmed: run `/sync-crate-skills`
### Manual Commands
- `/sync-crate-skills` - Sync all dependencies
- `/clean-crate-skills [crate]` - Remove skills
- `/update-crate-skill <crate>` - Update specific skill
## Architecture
```
Cargo.toml
Parse dependencies
For each crate:
├─ Check ~/.claude/skills/{crate}/
├─ If missing: Check actionbook for llms.txt
│ ├─ Found: /create-skills-via-llms
│ └─ Not found: /create-llms-for-skills first
└─ Load skill
```
## Local Skills Directory
```
~/.claude/skills/
├── tokio/
│ ├── SKILL.md
│ └── references/
├── serde/
│ ├── SKILL.md
│ └── references/
└── axum/
├── SKILL.md
└── references/
```
## Workflow Priority
1. **actionbook MCP** - Check for pre-generated llms.txt
2. **/create-llms-for-skills** - Generate llms.txt from docs.rs
3. **/create-skills-via-llms** - Create skills from llms.txt
## Workspace Support
For Cargo workspace projects:
1. Parse root Cargo.toml for `[workspace] members`
2. Collect all member Cargo.toml paths
3. Aggregate all dependencies
4. Deduplicate before skill generation
## Related Commands
- `/sync-crate-skills` - Main sync command
- `/clean-crate-skills` - Cleanup command
- `/update-crate-skill` - Update command
- `/create-llms-for-skills` - Generate llms.txt
- `/create-skills-via-llms` - Create skills from llms.txt

View File

@@ -1,99 +0,0 @@
---
name: core-fix-skill-docs
# Internal maintenance tool - no description to prevent auto-triggering
# Triggered by: /fix-skill-docs command
---
# Fix Skill Documentation
Check and fix missing reference files in dynamic skills.
## Usage
```
/fix-skill-docs [crate_name] [--check-only] [--remove-invalid]
```
**Arguments:**
- `crate_name`: Specific crate to check (optional, defaults to all)
- `--check-only`: Only report issues, don't fix
- `--remove-invalid`: Remove invalid references instead of creating files
## Instructions
### 1. Scan Skills Directory
```bash
# If crate_name provided
skill_dir=~/.claude/skills/{crate_name}
# Otherwise scan all
for dir in ~/.claude/skills/*/; do
# Process each skill
done
```
### 2. Parse SKILL.md for References
Extract referenced files from Documentation section:
```markdown
## Documentation
- `./references/file1.md` - Description
```
### 3. Check File Existence
```bash
if [ ! -f "{skill_dir}/references/{filename}" ]; then
echo "MISSING: {filename}"
fi
```
### 4. Report Status
```
=== {crate_name} ===
SKILL.md: ✅
references/:
- sync.md: ✅
- runtime.md: ❌ MISSING
Action needed: 1 file missing
```
### 5. Fix Missing Files
**--check-only**: Only report, don't fix.
**--remove-invalid**: Update SKILL.md to remove invalid references.
**Default**: Generate missing files using agent-browser:
```bash
agent-browser "Navigate to docs.rs/{crate_name}/latest/{crate_name}/{module}/
Extract documentation for {topic}. Save as markdown."
```
### 6. Update SKILL.md
Ensure Documentation section matches actual files.
## Tool Priority
1. **agent-browser CLI** - Generate missing documentation
2. **WebFetch** - Fallback if agent-browser unavailable
3. **Edit SKILL.md** - Remove invalid references (--remove-invalid)
## Example
```bash
# Check all skills
/fix-skill-docs --check-only
# Fix specific crate
/fix-skill-docs tokio
# Remove invalid references
/fix-skill-docs tokio --remove-invalid
```

View File

@@ -1,160 +0,0 @@
---
name: domain-cli
description: "Use when building CLI tools. Keywords: CLI, command line, terminal, clap, structopt, argument parsing, subcommand, interactive, TUI, ratatui, crossterm, indicatif, progress bar, colored output, shell completion, config file, environment variable, 命令行, 终端应用, 参数解析"
globs: ["**/Cargo.toml"]
---
# CLI Domain
> **Layer 3: Domain Constraints**
## Domain Constraints → Design Implications
| Domain Rule | Design Constraint | Rust Implication |
|-------------|-------------------|------------------|
| User ergonomics | Clear help, errors | clap derive macros |
| Config precedence | CLI > env > file | Layered config loading |
| Exit codes | Non-zero on error | Proper Result handling |
| Stdout/stderr | Data vs errors | eprintln! for errors |
| Interruptible | Handle Ctrl+C | Signal handling |
---
## Critical Constraints
### User Communication
```
RULE: Errors to stderr, data to stdout
WHY: Pipeable output, scriptability
RUST: eprintln! for errors, println! for data
```
### Configuration Priority
```
RULE: CLI args > env vars > config file > defaults
WHY: User expectation, override capability
RUST: Layered config with clap + figment/config
```
### Exit Codes
```
RULE: Return non-zero on any error
WHY: Script integration, automation
RUST: main() -> Result<(), Error> or explicit exit()
```
---
## Trace Down ↓
From constraints to design (Layer 2):
```
"Need argument parsing"
↓ m05-type-driven: Derive structs for args
↓ clap: #[derive(Parser)]
"Need config layering"
↓ m09-domain: Config as domain object
↓ figment/config: Layer sources
"Need progress display"
↓ m12-lifecycle: Progress bar as RAII
↓ indicatif: ProgressBar
```
---
## Key Crates
| Purpose | Crate |
|---------|-------|
| Argument parsing | clap |
| Interactive prompts | dialoguer |
| Progress bars | indicatif |
| Colored output | colored |
| Terminal UI | ratatui |
| Terminal control | crossterm |
| Console utilities | console |
## Design Patterns
| Pattern | Purpose | Implementation |
|---------|---------|----------------|
| Args struct | Type-safe args | `#[derive(Parser)]` |
| Subcommands | Command hierarchy | `#[derive(Subcommand)]` |
| Config layers | Override precedence | CLI > env > file |
| Progress | User feedback | `ProgressBar::new(len)` |
## Code Pattern: CLI Structure
```rust
use clap::{Parser, Subcommand};
#[derive(Parser)]
#[command(name = "myapp", about = "My CLI tool")]
struct Cli {
/// Enable verbose output
#[arg(short, long)]
verbose: bool,
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
/// Initialize a new project
Init { name: String },
/// Run the application
Run {
#[arg(short, long)]
port: Option<u16>,
},
}
fn main() -> anyhow::Result<()> {
let cli = Cli::parse();
match cli.command {
Commands::Init { name } => init_project(&name)?,
Commands::Run { port } => run_server(port.unwrap_or(8080))?,
}
Ok(())
}
```
---
## Common Mistakes
| Mistake | Domain Violation | Fix |
|---------|-----------------|-----|
| Errors to stdout | Breaks piping | eprintln! |
| No help text | Poor UX | #[arg(help = "...")] |
| Panic on error | Bad exit code | Result + proper handling |
| No progress for long ops | User uncertainty | indicatif |
---
## Trace to Layer 1
| Constraint | Layer 2 Pattern | Layer 1 Implementation |
|------------|-----------------|------------------------|
| Type-safe args | Derive macros | clap Parser |
| Error handling | Result propagation | anyhow + exit codes |
| User feedback | Progress RAII | indicatif ProgressBar |
| Config precedence | Builder pattern | Layered sources |
---
## Related Skills
| When | See |
|------|-----|
| Error handling | m06-error-handling |
| Type-driven args | m05-type-driven |
| Progress lifecycle | m12-lifecycle |
| Async CLI | m07-concurrency |

View File

@@ -1,165 +0,0 @@
---
name: domain-cloud-native
description: "Use when building cloud-native apps. Keywords: kubernetes, k8s, docker, container, grpc, tonic, microservice, service mesh, observability, tracing, metrics, health check, cloud, deployment, 云原生, 微服务, 容器"
---
# Cloud-Native Domain
> **Layer 3: Domain Constraints**
## Domain Constraints → Design Implications
| Domain Rule | Design Constraint | Rust Implication |
|-------------|-------------------|------------------|
| 12-Factor | Config from env | Environment-based config |
| Observability | Metrics + traces | tracing + opentelemetry |
| Health checks | Liveness/readiness | Dedicated endpoints |
| Graceful shutdown | Clean termination | Signal handling |
| Horizontal scale | Stateless design | No local state |
| Container-friendly | Small binaries | Release optimization |
---
## Critical Constraints
### Stateless Design
```
RULE: No local persistent state
WHY: Pods can be killed/rescheduled anytime
RUST: External state (Redis, DB), no static mut
```
### Graceful Shutdown
```
RULE: Handle SIGTERM, drain connections
WHY: Zero-downtime deployments
RUST: tokio::signal + graceful shutdown
```
### Observability
```
RULE: Every request must be traceable
WHY: Debugging distributed systems
RUST: tracing spans, opentelemetry export
```
---
## Trace Down ↓
From constraints to design (Layer 2):
```
"Need distributed tracing"
↓ m12-lifecycle: Span lifecycle
↓ tracing + opentelemetry
"Need graceful shutdown"
↓ m07-concurrency: Signal handling
↓ m12-lifecycle: Connection draining
"Need health checks"
↓ domain-web: HTTP endpoints
↓ m06-error-handling: Health status
```
---
## Key Crates
| Purpose | Crate |
|---------|-------|
| gRPC | tonic |
| Kubernetes | kube, kube-runtime |
| Docker | bollard |
| Tracing | tracing, opentelemetry |
| Metrics | prometheus, metrics |
| Config | config, figment |
| Health | HTTP endpoints |
## Design Patterns
| Pattern | Purpose | Implementation |
|---------|---------|----------------|
| gRPC services | Service mesh | tonic + tower |
| K8s operators | Custom resources | kube-runtime Controller |
| Observability | Debugging | tracing + OTEL |
| Health checks | Orchestration | `/health`, `/ready` |
| Config | 12-factor | Env vars + secrets |
## Code Pattern: Graceful Shutdown
```rust
use tokio::signal;
async fn run_server() -> anyhow::Result<()> {
let app = Router::new()
.route("/health", get(health))
.route("/ready", get(ready));
let addr = SocketAddr::from(([0, 0, 0, 0], 8080));
axum::Server::bind(&addr)
.serve(app.into_make_service())
.with_graceful_shutdown(shutdown_signal())
.await?;
Ok(())
}
async fn shutdown_signal() {
signal::ctrl_c().await.expect("failed to listen for ctrl+c");
tracing::info!("shutdown signal received");
}
```
## Health Check Pattern
```rust
async fn health() -> StatusCode {
StatusCode::OK
}
async fn ready(State(db): State<Arc<DbPool>>) -> StatusCode {
match db.ping().await {
Ok(_) => StatusCode::OK,
Err(_) => StatusCode::SERVICE_UNAVAILABLE,
}
}
```
---
## Common Mistakes
| Mistake | Domain Violation | Fix |
|---------|-----------------|-----|
| Local file state | Not stateless | External storage |
| No SIGTERM handling | Hard kills | Graceful shutdown |
| No tracing | Can't debug | tracing spans |
| Static config | Not 12-factor | Env vars |
---
## Trace to Layer 1
| Constraint | Layer 2 Pattern | Layer 1 Implementation |
|------------|-----------------|------------------------|
| Stateless | External state | Arc<Client> for external |
| Graceful shutdown | Signal handling | tokio::signal |
| Tracing | Span lifecycle | tracing + OTEL |
| Health checks | HTTP endpoints | Dedicated routes |
---
## Related Skills
| When | See |
|------|-----|
| Async patterns | m07-concurrency |
| HTTP endpoints | domain-web |
| Error handling | m13-domain-error |
| Resource lifecycle | m12-lifecycle |

View File

@@ -1,170 +0,0 @@
---
name: domain-embedded
description: "Use when developing embedded/no_std Rust. Keywords: embedded, no_std, microcontroller, MCU, ARM, RISC-V, bare metal, firmware, HAL, PAC, RTIC, embassy, interrupt, DMA, peripheral, GPIO, SPI, I2C, UART, embedded-hal, cortex-m, esp32, stm32, nrf, 嵌入式, 单片机, 固件, 裸机"
globs: ["**/Cargo.toml", "**/.cargo/config.toml"]
---
# Embedded Domain
> **Layer 3: Domain Constraints**
## Domain Constraints → Design Implications
| Domain Rule | Design Constraint | Rust Implication |
|-------------|-------------------|------------------|
| No heap | Stack allocation | heapless, no Box/Vec |
| No std | Core only | #![no_std] |
| Real-time | Predictable timing | No dynamic alloc |
| Resource limited | Minimal memory | Static buffers |
| Hardware safety | Safe peripheral access | HAL + ownership |
| Interrupt safe | No blocking in ISR | Atomic, critical sections |
---
## Critical Constraints
### No Dynamic Allocation
```
RULE: Cannot use heap (no allocator)
WHY: Deterministic memory, no OOM
RUST: heapless::Vec<T, N>, arrays
```
### Interrupt Safety
```
RULE: Shared state must be interrupt-safe
WHY: ISR can preempt at any time
RUST: Mutex<RefCell<T>> + critical section
```
### Hardware Ownership
```
RULE: Peripherals must have clear ownership
WHY: Prevent conflicting access
RUST: HAL takes ownership, singletons
```
---
## Trace Down ↓
From constraints to design (Layer 2):
```
"Need no_std compatible data structures"
↓ m02-resource: heapless collections
↓ Static sizing: heapless::Vec<T, N>
"Need interrupt-safe state"
↓ m03-mutability: Mutex<RefCell<Option<T>>>
↓ m07-concurrency: Critical sections
"Need peripheral ownership"
↓ m01-ownership: Singleton pattern
↓ m12-lifecycle: RAII for hardware
```
---
## Layer Stack
| Layer | Examples | Purpose |
|-------|----------|---------|
| PAC | stm32f4, esp32c3 | Register access |
| HAL | stm32f4xx-hal | Hardware abstraction |
| Framework | RTIC, Embassy | Concurrency |
| Traits | embedded-hal | Portable drivers |
## Framework Comparison
| Framework | Style | Best For |
|-----------|-------|----------|
| RTIC | Priority-based | Interrupt-driven apps |
| Embassy | Async | Complex state machines |
| Bare metal | Manual | Simple apps |
## Key Crates
| Purpose | Crate |
|---------|-------|
| Runtime (ARM) | cortex-m-rt |
| Panic handler | panic-halt, panic-probe |
| Collections | heapless |
| HAL traits | embedded-hal |
| Logging | defmt |
| Flash/debug | probe-run |
## Design Patterns
| Pattern | Purpose | Implementation |
|---------|---------|----------------|
| no_std setup | Bare metal | `#![no_std]` + `#![no_main]` |
| Entry point | Startup | `#[entry]` or embassy |
| Static state | ISR access | `Mutex<RefCell<Option<T>>>` |
| Fixed buffers | No heap | `heapless::Vec<T, N>` |
## Code Pattern: Static Peripheral
```rust
#![no_std]
#![no_main]
use cortex_m::interrupt::{self, Mutex};
use core::cell::RefCell;
static LED: Mutex<RefCell<Option<Led>>> = Mutex::new(RefCell::new(None));
#[entry]
fn main() -> ! {
let dp = pac::Peripherals::take().unwrap();
let led = Led::new(dp.GPIOA);
interrupt::free(|cs| {
LED.borrow(cs).replace(Some(led));
});
loop {
interrupt::free(|cs| {
if let Some(led) = LED.borrow(cs).borrow_mut().as_mut() {
led.toggle();
}
});
}
}
```
---
## Common Mistakes
| Mistake | Domain Violation | Fix |
|---------|-----------------|-----|
| Using Vec | Heap allocation | heapless::Vec |
| No critical section | Race with ISR | Mutex + interrupt::free |
| Blocking in ISR | Missed interrupts | Defer to main loop |
| Unsafe peripheral | Hardware conflict | HAL ownership |
---
## Trace to Layer 1
| Constraint | Layer 2 Pattern | Layer 1 Implementation |
|------------|-----------------|------------------------|
| No heap | Static collections | heapless::Vec<T, N> |
| ISR safety | Critical sections | Mutex<RefCell<T>> |
| Hardware ownership | Singleton | take().unwrap() |
| no_std | Core-only | #![no_std], #![no_main] |
---
## Related Skills
| When | See |
|------|-----|
| Static memory | m02-resource |
| Interior mutability | m03-mutability |
| Interrupt patterns | m07-concurrency |
| Unsafe for hardware | unsafe-checker |

View File

@@ -1,145 +0,0 @@
---
name: domain-fintech
description: "Use when building fintech apps. Keywords: fintech, trading, decimal, currency, financial, money, transaction, ledger, payment, exchange rate, precision, rounding, accounting, 金融, 交易系统, 货币, 支付"
---
# FinTech Domain
> **Layer 3: Domain Constraints**
## Domain Constraints → Design Implications
| Domain Rule | Design Constraint | Rust Implication |
|-------------|-------------------|------------------|
| Audit trail | Immutable records | Arc<T>, no mutation |
| Precision | No floating point | rust_decimal |
| Consistency | Transaction boundaries | Clear ownership |
| Compliance | Complete logging | Structured tracing |
| Reproducibility | Deterministic execution | No race conditions |
---
## Critical Constraints
### Financial Precision
```
RULE: Never use f64 for money
WHY: Floating point loses precision
RUST: Use rust_decimal::Decimal
```
### Audit Requirements
```
RULE: All transactions must be immutable and traceable
WHY: Regulatory compliance, dispute resolution
RUST: Arc<T> for sharing, event sourcing pattern
```
### Consistency
```
RULE: Money can't disappear or appear
WHY: Double-entry accounting principles
RUST: Transaction types with validated totals
```
---
## Trace Down ↓
From constraints to design (Layer 2):
```
"Need immutable transaction records"
↓ m09-domain: Model as Value Objects
↓ m01-ownership: Use Arc for shared immutable data
"Need precise decimal math"
↓ m05-type-driven: Newtype for Currency/Amount
↓ rust_decimal: Use Decimal type
"Need transaction boundaries"
↓ m12-lifecycle: RAII for transaction scope
↓ m09-domain: Aggregate boundaries
```
---
## Key Crates
| Purpose | Crate |
|---------|-------|
| Decimal math | rust_decimal |
| Date/time | chrono, time |
| UUID | uuid |
| Serialization | serde |
| Validation | validator |
## Design Patterns
| Pattern | Purpose | Implementation |
|---------|---------|----------------|
| Currency newtype | Type safety | `struct Amount(Decimal);` |
| Transaction | Atomic operations | Event sourcing |
| Audit log | Traceability | Structured logging with trace IDs |
| Ledger | Double-entry | Debit/credit balance |
## Code Pattern: Currency Type
```rust
use rust_decimal::Decimal;
#[derive(Clone, Debug, PartialEq)]
pub struct Amount {
value: Decimal,
currency: Currency,
}
impl Amount {
pub fn new(value: Decimal, currency: Currency) -> Self {
Self { value, currency }
}
pub fn add(&self, other: &Amount) -> Result<Amount, CurrencyMismatch> {
if self.currency != other.currency {
return Err(CurrencyMismatch);
}
Ok(Amount::new(self.value + other.value, self.currency))
}
}
```
---
## Common Mistakes
| Mistake | Domain Violation | Fix |
|---------|-----------------|-----|
| Using f64 | Precision loss | rust_decimal |
| Mutable transaction | Audit trail broken | Immutable + events |
| String for amount | No validation | Validated newtype |
| Silent overflow | Money disappears | Checked arithmetic |
---
## Trace to Layer 1
| Constraint | Layer 2 Pattern | Layer 1 Implementation |
|------------|-----------------|------------------------|
| Immutable records | Event sourcing | Arc<T>, Clone |
| Transaction scope | Aggregate | Owned children |
| Precision | Value Object | rust_decimal newtype |
| Thread-safe sharing | Shared immutable | Arc (not Rc) |
---
## Related Skills
| When | See |
|------|-----|
| Value Object design | m09-domain |
| Ownership for immutable | m01-ownership |
| Arc for sharing | m02-resource |
| Error handling | m13-domain-error |

View File

@@ -1,167 +0,0 @@
---
name: domain-iot
description: "Use when building IoT apps. Keywords: IoT, Internet of Things, sensor, MQTT, device, edge computing, telemetry, actuator, smart home, gateway, protocol, 物联网, 传感器, 边缘计算, 智能家居"
---
# IoT Domain
> **Layer 3: Domain Constraints**
## Domain Constraints → Design Implications
| Domain Rule | Design Constraint | Rust Implication |
|-------------|-------------------|------------------|
| Unreliable network | Offline-first | Local buffering |
| Power constraints | Efficient code | Sleep modes, minimal alloc |
| Resource limits | Small footprint | no_std where needed |
| Security | Encrypted comms | TLS, signed firmware |
| Reliability | Self-recovery | Watchdog, error handling |
| OTA updates | Safe upgrades | Rollback capability |
---
## Critical Constraints
### Network Unreliability
```
RULE: Network can fail at any time
WHY: Wireless, remote locations
RUST: Local queue, retry with backoff
```
### Power Management
```
RULE: Minimize power consumption
WHY: Battery life, energy costs
RUST: Sleep modes, efficient algorithms
```
### Device Security
```
RULE: All communication encrypted
WHY: Physical access possible
RUST: TLS, signed messages
```
---
## Trace Down ↓
From constraints to design (Layer 2):
```
"Need offline-first design"
↓ m12-lifecycle: Local buffer with persistence
↓ m13-domain-error: Retry with backoff
"Need power efficiency"
↓ domain-embedded: no_std patterns
↓ m10-performance: Minimal allocations
"Need reliable messaging"
↓ m07-concurrency: Async with timeout
↓ MQTT: QoS levels
```
---
## Environment Comparison
| Environment | Stack | Crates |
|-------------|-------|--------|
| Linux gateway | tokio + std | rumqttc, reqwest |
| MCU device | embassy + no_std | embedded-hal |
| Hybrid | Split workloads | Both |
## Key Crates
| Purpose | Crate |
|---------|-------|
| MQTT (std) | rumqttc, paho-mqtt |
| Embedded | embedded-hal, embassy |
| Async (std) | tokio |
| Async (no_std) | embassy |
| Logging (no_std) | defmt |
| Logging (std) | tracing |
## Design Patterns
| Pattern | Purpose | Implementation |
|---------|---------|----------------|
| Pub/Sub | Device comms | MQTT topics |
| Edge compute | Local processing | Filter before upload |
| OTA updates | Firmware upgrade | Signed + rollback |
| Power mgmt | Battery life | Sleep + wake events |
| Store & forward | Network reliability | Local queue |
## Code Pattern: MQTT Client
```rust
use rumqttc::{AsyncClient, MqttOptions, QoS};
async fn run_mqtt() -> anyhow::Result<()> {
let mut options = MqttOptions::new("device-1", "broker.example.com", 1883);
options.set_keep_alive(Duration::from_secs(30));
let (client, mut eventloop) = AsyncClient::new(options, 10);
// Subscribe to commands
client.subscribe("devices/device-1/commands", QoS::AtLeastOnce).await?;
// Publish telemetry
tokio::spawn(async move {
loop {
let data = read_sensor().await;
client.publish("devices/device-1/telemetry", QoS::AtLeastOnce, false, data).await.ok();
tokio::time::sleep(Duration::from_secs(60)).await;
}
});
// Process events
loop {
match eventloop.poll().await {
Ok(event) => handle_event(event).await,
Err(e) => {
tracing::error!("MQTT error: {}", e);
tokio::time::sleep(Duration::from_secs(5)).await;
}
}
}
}
```
---
## Common Mistakes
| Mistake | Domain Violation | Fix |
|---------|-----------------|-----|
| No retry logic | Lost data | Exponential backoff |
| Always-on radio | Battery drain | Sleep between sends |
| Unencrypted MQTT | Security risk | TLS |
| No local buffer | Network outage = data loss | Persist locally |
---
## Trace to Layer 1
| Constraint | Layer 2 Pattern | Layer 1 Implementation |
|------------|-----------------|------------------------|
| Offline-first | Store & forward | Local queue + flush |
| Power efficiency | Sleep patterns | Timer-based wake |
| Network reliability | Retry | tokio-retry, backoff |
| Security | TLS | rustls, native-tls |
---
## Related Skills
| When | See |
|------|-----|
| Embedded patterns | domain-embedded |
| Async patterns | m07-concurrency |
| Error recovery | m13-domain-error |
| Performance | m10-performance |

View File

@@ -1,180 +0,0 @@
---
name: domain-ml
description: "Use when building ML/AI apps in Rust. Keywords: machine learning, ML, AI, tensor, model, inference, neural network, deep learning, training, prediction, ndarray, tch-rs, burn, candle, 机器学习, 人工智能, 模型推理"
---
# Machine Learning Domain
> **Layer 3: Domain Constraints**
## Domain Constraints → Design Implications
| Domain Rule | Design Constraint | Rust Implication |
|-------------|-------------------|------------------|
| Large data | Efficient memory | Zero-copy, streaming |
| GPU acceleration | CUDA/Metal support | candle, tch-rs |
| Model portability | Standard formats | ONNX |
| Batch processing | Throughput over latency | Batched inference |
| Numerical precision | Float handling | ndarray, careful f32/f64 |
| Reproducibility | Deterministic | Seeded random, versioning |
---
## Critical Constraints
### Memory Efficiency
```
RULE: Avoid copying large tensors
WHY: Memory bandwidth is bottleneck
RUST: References, views, in-place ops
```
### GPU Utilization
```
RULE: Batch operations for GPU efficiency
WHY: GPU overhead per kernel launch
RUST: Batch sizes, async data loading
```
### Model Portability
```
RULE: Use standard model formats
WHY: Train in Python, deploy in Rust
RUST: ONNX via tract or candle
```
---
## Trace Down ↓
From constraints to design (Layer 2):
```
"Need efficient data pipelines"
↓ m10-performance: Streaming, batching
↓ polars: Lazy evaluation
"Need GPU inference"
↓ m07-concurrency: Async data loading
↓ candle/tch-rs: CUDA backend
"Need model loading"
↓ m12-lifecycle: Lazy init, caching
↓ tract: ONNX runtime
```
---
## Use Case → Framework
| Use Case | Recommended | Why |
|----------|-------------|-----|
| Inference only | tract (ONNX) | Lightweight, portable |
| Training + inference | candle, burn | Pure Rust, GPU |
| PyTorch models | tch-rs | Direct bindings |
| Data pipelines | polars | Fast, lazy eval |
## Key Crates
| Purpose | Crate |
|---------|-------|
| Tensors | ndarray |
| ONNX inference | tract |
| ML framework | candle, burn |
| PyTorch bindings | tch-rs |
| Data processing | polars |
| Embeddings | fastembed |
## Design Patterns
| Pattern | Purpose | Implementation |
|---------|---------|----------------|
| Model loading | Once, reuse | `OnceLock<Model>` |
| Batching | Throughput | Collect then process |
| Streaming | Large data | Iterator-based |
| GPU async | Parallelism | Data loading parallel to compute |
## Code Pattern: Inference Server
```rust
use std::sync::OnceLock;
use tract_onnx::prelude::*;
static MODEL: OnceLock<SimplePlan<TypedFact, Box<dyn TypedOp>, Graph<TypedFact, Box<dyn TypedOp>>>> = OnceLock::new();
fn get_model() -> &'static SimplePlan<...> {
MODEL.get_or_init(|| {
tract_onnx::onnx()
.model_for_path("model.onnx")
.unwrap()
.into_optimized()
.unwrap()
.into_runnable()
.unwrap()
})
}
async fn predict(input: Vec<f32>) -> anyhow::Result<Vec<f32>> {
let model = get_model();
let input = tract_ndarray::arr1(&input).into_shape((1, input.len()))?;
let result = model.run(tvec!(input.into()))?;
Ok(result[0].to_array_view::<f32>()?.iter().copied().collect())
}
```
## Code Pattern: Batched Inference
```rust
async fn batch_predict(inputs: Vec<Vec<f32>>, batch_size: usize) -> Vec<Vec<f32>> {
let mut results = Vec::with_capacity(inputs.len());
for batch in inputs.chunks(batch_size) {
// Stack inputs into batch tensor
let batch_tensor = stack_inputs(batch);
// Run inference on batch
let batch_output = model.run(batch_tensor).await;
// Unstack results
results.extend(unstack_outputs(batch_output));
}
results
}
```
---
## Common Mistakes
| Mistake | Domain Violation | Fix |
|---------|-----------------|-----|
| Clone tensors | Memory waste | Use views |
| Single inference | GPU underutilized | Batch processing |
| Load model per request | Slow | Singleton pattern |
| Sync data loading | GPU idle | Async pipeline |
---
## Trace to Layer 1
| Constraint | Layer 2 Pattern | Layer 1 Implementation |
|------------|-----------------|------------------------|
| Memory efficiency | Zero-copy | ndarray views |
| Model singleton | Lazy init | OnceLock<Model> |
| Batch processing | Chunked iteration | chunks() + parallel |
| GPU async | Concurrent loading | tokio::spawn + GPU |
---
## Related Skills
| When | See |
|------|-----|
| Performance | m10-performance |
| Lazy initialization | m12-lifecycle |
| Async patterns | m07-concurrency |
| Memory efficiency | m01-ownership |

View File

@@ -1,155 +0,0 @@
---
name: domain-web
description: "Use when building web services. Keywords: web server, HTTP, REST API, GraphQL, WebSocket, axum, actix, warp, rocket, tower, hyper, reqwest, middleware, router, handler, extractor, state management, authentication, authorization, JWT, session, cookie, CORS, rate limiting, web 开发, HTTP 服务, API 设计, 中间件, 路由"
globs: ["**/Cargo.toml"]
---
# Web Domain
> **Layer 3: Domain Constraints**
## Domain Constraints → Design Implications
| Domain Rule | Design Constraint | Rust Implication |
|-------------|-------------------|------------------|
| Stateless HTTP | No request-local globals | State in extractors |
| Concurrency | Handle many connections | Async, Send + Sync |
| Latency SLA | Fast response | Efficient ownership |
| Security | Input validation | Type-safe extractors |
| Observability | Request tracing | tracing + tower layers |
---
## Critical Constraints
### Async by Default
```
RULE: Web handlers must not block
WHY: Block one task = block many requests
RUST: async/await, spawn_blocking for CPU work
```
### State Management
```
RULE: Shared state must be thread-safe
WHY: Handlers run on any thread
RUST: Arc<T>, Arc<RwLock<T>> for mutable
```
### Request Lifecycle
```
RULE: Resources live only for request duration
WHY: Memory management, no leaks
RUST: Extractors, proper ownership
```
---
## Trace Down ↓
From constraints to design (Layer 2):
```
"Need shared application state"
↓ m07-concurrency: Use Arc for thread-safe sharing
↓ m02-resource: Arc<RwLock<T>> for mutable state
"Need request validation"
↓ m05-type-driven: Validated extractors
↓ m06-error-handling: IntoResponse for errors
"Need middleware stack"
↓ m12-lifecycle: Tower layers
↓ m04-zero-cost: Trait-based composition
```
---
## Framework Comparison
| Framework | Style | Best For |
|-----------|-------|----------|
| axum | Functional, tower | Modern APIs |
| actix-web | Actor-based | High performance |
| warp | Filter composition | Composable APIs |
| rocket | Macro-driven | Rapid development |
## Key Crates
| Purpose | Crate |
|---------|-------|
| HTTP server | axum, actix-web |
| HTTP client | reqwest |
| JSON | serde_json |
| Auth/JWT | jsonwebtoken |
| Session | tower-sessions |
| Database | sqlx, diesel |
| Middleware | tower |
## Design Patterns
| Pattern | Purpose | Implementation |
|---------|---------|----------------|
| Extractors | Request parsing | `State(db)`, `Json(payload)` |
| Error response | Unified errors | `impl IntoResponse` |
| Middleware | Cross-cutting | Tower layers |
| Shared state | App config | `Arc<AppState>` |
## Code Pattern: Axum Handler
```rust
async fn handler(
State(db): State<Arc<DbPool>>,
Json(payload): Json<CreateUser>,
) -> Result<Json<User>, AppError> {
let user = db.create_user(&payload).await?;
Ok(Json(user))
}
// Error handling
impl IntoResponse for AppError {
fn into_response(self) -> Response {
let (status, message) = match self {
Self::NotFound => (StatusCode::NOT_FOUND, "Not found"),
Self::Internal(_) => (StatusCode::INTERNAL_SERVER_ERROR, "Internal error"),
};
(status, Json(json!({"error": message}))).into_response()
}
}
```
---
## Common Mistakes
| Mistake | Domain Violation | Fix |
|---------|-----------------|-----|
| Blocking in handler | Latency spike | spawn_blocking |
| Rc in state | Not Send + Sync | Use Arc |
| No validation | Security risk | Type-safe extractors |
| No error response | Bad UX | IntoResponse impl |
---
## Trace to Layer 1
| Constraint | Layer 2 Pattern | Layer 1 Implementation |
|------------|-----------------|------------------------|
| Async handlers | Async/await | tokio runtime |
| Thread-safe state | Shared state | Arc<T>, Arc<RwLock<T>> |
| Request lifecycle | Extractors | Ownership via From<Request> |
| Middleware | Tower layers | Trait-based composition |
---
## Related Skills
| When | See |
|------|-----|
| Async patterns | m07-concurrency |
| State management | m02-resource |
| Error handling | m06-error-handling |
| Middleware design | m12-lifecycle |

View File

@@ -1,133 +0,0 @@
---
name: m01-ownership
description: "CRITICAL: Use for ownership/borrow/lifetime issues. Triggers: E0382, E0597, E0506, E0507, E0515, E0716, E0106, value moved, borrowed value does not live long enough, cannot move out of, use of moved value, ownership, borrow, lifetime, 'a, 'static, move, clone, Copy, 所有权, 借用, 生命周期"
---
# Ownership & Lifetimes
> **Layer 1: Language Mechanics**
## Core Question
**Who should own this data, and for how long?**
Before fixing ownership errors, understand the data's role:
- Is it shared or exclusive?
- Is it short-lived or long-lived?
- Is it transformed or just read?
---
## Error → Design Question
| Error | Don't Just Say | Ask Instead |
|-------|----------------|-------------|
| E0382 | "Clone it" | Who should own this data? |
| E0597 | "Extend lifetime" | Is the scope boundary correct? |
| E0506 | "End borrow first" | Should mutation happen elsewhere? |
| E0507 | "Clone before move" | Why are we moving from a reference? |
| E0515 | "Return owned" | Should caller own the data? |
| E0716 | "Bind to variable" | Why is this temporary? |
| E0106 | "Add 'a" | What is the actual lifetime relationship? |
---
## Thinking Prompt
Before fixing an ownership error, ask:
1. **What is this data's domain role?**
- Entity (unique identity) → owned
- Value Object (interchangeable) → clone/copy OK
- Temporary (computation result) → maybe restructure
2. **Is the ownership design intentional?**
- By design → work within constraints
- Accidental → consider redesign
3. **Fix symptom or redesign?**
- If Strike 3 (3rd attempt) → escalate to Layer 2
---
## Trace Up ↑
When errors persist, trace to design layer:
```
E0382 (moved value)
↑ Ask: What design choice led to this ownership pattern?
↑ Check: m09-domain (is this Entity or Value Object?)
↑ Check: domain-* (what constraints apply?)
```
| Persistent Error | Trace To | Question |
|-----------------|----------|----------|
| E0382 repeated | m02-resource | Should use Arc/Rc for sharing? |
| E0597 repeated | m09-domain | Is scope boundary at right place? |
| E0506/E0507 | m03-mutability | Should use interior mutability? |
---
## Trace Down ↓
From design decisions to implementation:
```
"Data needs to be shared immutably"
↓ Use: Arc<T> (multi-thread) or Rc<T> (single-thread)
"Data needs exclusive ownership"
↓ Use: move semantics, take ownership
"Data is read-only view"
↓ Use: &T (immutable borrow)
```
---
## Quick Reference
| Pattern | Ownership | Cost | Use When |
|---------|-----------|------|----------|
| Move | Transfer | Zero | Caller doesn't need data |
| `&T` | Borrow | Zero | Read-only access |
| `&mut T` | Exclusive borrow | Zero | Need to modify |
| `clone()` | Duplicate | Alloc + copy | Actually need a copy |
| `Rc<T>` | Shared (single) | Ref count | Single-thread sharing |
| `Arc<T>` | Shared (multi) | Atomic ref count | Multi-thread sharing |
| `Cow<T>` | Clone-on-write | Alloc if mutated | Might modify |
## Error Code Reference
| Error | Cause | Quick Fix |
|-------|-------|-----------|
| E0382 | Value moved | Clone, reference, or redesign ownership |
| E0597 | Reference outlives owner | Extend owner scope or restructure |
| E0506 | Assign while borrowed | End borrow before mutation |
| E0507 | Move out of borrowed | Clone or use reference |
| E0515 | Return local reference | Return owned value |
| E0716 | Temporary dropped | Bind to variable |
| E0106 | Missing lifetime | Add `'a` annotation |
---
## Anti-Patterns
| Anti-Pattern | Why Bad | Better |
|--------------|---------|--------|
| `.clone()` everywhere | Hides design issues | Design ownership properly |
| Fight borrow checker | Increases complexity | Work with the compiler |
| `'static` for everything | Restricts flexibility | Use appropriate lifetimes |
| Leak with `Box::leak` | Memory leak | Proper lifetime design |
---
## Related Skills
| When | See |
|------|-----|
| Need smart pointers | m02-resource |
| Need interior mutability | m03-mutability |
| Data is domain entity | m09-domain |
| Learning ownership concepts | m14-mental-model |

View File

@@ -1,222 +0,0 @@
# Ownership: Comparison with Other Languages
## Rust vs C++
### Memory Management
| Aspect | Rust | C++ |
|--------|------|-----|
| Default | Move semantics | Copy semantics (pre-C++11) |
| Move | `let b = a;` (a invalidated) | `auto b = std::move(a);` (a valid but unspecified) |
| Copy | `let b = a.clone();` | `auto b = a;` |
| Safety | Compile-time enforcement | Runtime responsibility |
### Rust Move vs C++ Move
```rust
// Rust: after move, 'a' is INVALID
let a = String::from("hello");
let b = a; // a moved
// println!("{}", a); // COMPILE ERROR
// Equivalent in C++:
// std::string a = "hello";
// std::string b = std::move(a);
// std::cout << a; // UNDEFINED (compiles but buggy)
```
### Smart Pointers
| Rust | C++ | Purpose |
|------|-----|---------|
| `Box<T>` | `std::unique_ptr<T>` | Unique ownership |
| `Rc<T>` | `std::shared_ptr<T>` | Shared ownership |
| `Arc<T>` | `std::shared_ptr<T>` + atomic | Thread-safe shared |
| `RefCell<T>` | (manual runtime checks) | Interior mutability |
---
## Rust vs Go
### Memory Model
| Aspect | Rust | Go |
|--------|------|-----|
| Memory | Stack + heap, explicit | GC manages all |
| Ownership | Enforced at compile-time | None (GC handles) |
| Null | `Option<T>` | `nil` for pointers |
| Concurrency | `Send`/`Sync` traits | Channels (less strict) |
### Sharing Data
```rust
// Rust: explicit about sharing
use std::sync::Arc;
let data = Arc::new(vec![1, 2, 3]);
let data_clone = Arc::clone(&data);
std::thread::spawn(move || {
println!("{:?}", data_clone);
});
// Go: implicit sharing
// data := []int{1, 2, 3}
// go func() {
// fmt.Println(data) // potential race condition
// }()
```
### Why No GC in Rust
1. **Deterministic destruction**: Resources freed exactly when scope ends
2. **Zero-cost**: No GC pauses or overhead
3. **Embeddable**: Works in OS kernels, embedded systems
4. **Predictable latency**: Critical for real-time systems
---
## Rust vs Java/C#
### Reference Semantics
| Aspect | Rust | Java/C# |
|--------|------|---------|
| Objects | Owned by default | Reference by default |
| Null | `Option<T>` | `null` (nullable) |
| Immutability | Default | Must use `final`/`readonly` |
| Copy | Explicit `.clone()` | Reference copy (shallow) |
### Comparison
```rust
// Rust: clear ownership
fn process(data: Vec<i32>) { // takes ownership
// data is ours, will be freed at end
}
let numbers = vec![1, 2, 3];
process(numbers);
// numbers is invalid here
// Java: ambiguous ownership
// void process(List<Integer> data) {
// // Who owns data? Caller? Callee? Both?
// // Can caller still use it?
// }
```
---
## Rust vs Python
### Memory Model
| Aspect | Rust | Python |
|--------|------|--------|
| Typing | Static, compile-time | Dynamic, runtime |
| Memory | Ownership-based | Reference counting + GC |
| Mutability | Default immutable | Default mutable |
| Performance | Native, zero-cost | Interpreted, higher overhead |
### Common Pattern Translation
```rust
// Rust: borrowing iteration
let items = vec!["a", "b", "c"];
for item in &items {
println!("{}", item);
}
// items still usable
// Python: iteration doesn't consume
// items = ["a", "b", "c"]
// for item in items:
// print(item)
// items still usable (different reason - ref counting)
```
---
## Unique Rust Concepts
### Concepts Other Languages Lack
1. **Borrow Checker**: No other mainstream language has compile-time borrow checking
2. **Lifetimes**: Explicit annotation of reference validity
3. **Move by Default**: Values move, not copy
4. **No Null**: `Option<T>` instead of null pointers
5. **Affine Types**: Values can be used at most once
### Learning Curve Areas
| Concept | Coming From | Key Insight |
|---------|-------------|-------------|
| Ownership | GC languages | Think about who "owns" data |
| Borrowing | C/C++ | Like references but checked |
| Lifetimes | Any | Explicit scope of validity |
| Move | C++ | Move is default, not copy |
---
## Mental Model Shifts
### From GC Languages (Java, Go, Python)
```
Before: "Memory just works, GC handles it"
After: "I explicitly decide who owns data and when it's freed"
```
Key shifts:
- Think about ownership at design time
- Returning references requires lifetime thinking
- No more `null` - use `Option<T>`
### From C/C++
```
Before: "I manually manage memory and hope I get it right"
After: "Compiler enforces correctness, I fight the borrow checker"
```
Key shifts:
- Trust the compiler's errors
- Move is the default (unlike C++ copy)
- Smart pointers are idiomatic, not overhead
### From Functional Languages (Haskell, ML)
```
Before: "Everything is immutable, copying is fine"
After: "Mutability is explicit, ownership prevents aliasing"
```
Key shifts:
- Mutability is safe because of ownership rules
- No persistent data structures needed (usually)
- Performance characteristics are explicit
---
## Performance Trade-offs
| Language | Memory Overhead | Latency | Throughput |
|----------|-----------------|---------|------------|
| Rust | Minimal (no GC) | Predictable | Excellent |
| C++ | Minimal | Predictable | Excellent |
| Go | GC overhead | GC pauses | Good |
| Java | GC overhead | GC pauses | Good |
| Python | High (ref counting + GC) | Variable | Lower |
### When Rust Ownership Wins
1. **Real-time systems**: No GC pauses
2. **Embedded**: No runtime overhead
3. **High-performance**: Zero-cost abstractions
4. **Concurrent**: Data races prevented at compile time
### When GC Might Be Preferable
1. **Rapid prototyping**: Less mental overhead
2. **Complex object graphs**: Cycles are tricky in Rust
3. **GUI applications**: Object lifetimes are dynamic
4. **Small programs**: Overhead doesn't matter

View File

@@ -1,339 +0,0 @@
# Ownership Best Practices
## API Design Patterns
### 1. Prefer Borrowing Over Ownership
```rust
// BAD: takes ownership unnecessarily
fn print_name(name: String) {
println!("Name: {}", name);
}
// GOOD: borrows instead
fn print_name(name: &str) {
println!("Name: {}", name);
}
// Caller benefits:
let name = String::from("Alice");
print_name(&name); // can reuse name
print_name(&name); // still valid
```
### 2. Return Owned Values from Constructors
```rust
// GOOD: return owned value
impl User {
fn new(name: &str) -> Self {
User {
name: name.to_string(),
}
}
}
// GOOD: accept Into<String> for flexibility
impl User {
fn new(name: impl Into<String>) -> Self {
User {
name: name.into(),
}
}
}
// Usage:
let u1 = User::new("Alice"); // &str
let u2 = User::new(String::from("Bob")); // String
```
### 3. Use AsRef for Generic Borrowing
```rust
// GOOD: accepts both &str and String
fn process<S: AsRef<str>>(input: S) {
let s = input.as_ref();
println!("{}", s);
}
process("literal"); // &str
process(String::from("owned")); // String
process(&String::from("ref")); // &String
```
### 4. Cow for Clone-on-Write
```rust
use std::borrow::Cow;
// Return borrowed when possible, owned when needed
fn maybe_modify(s: &str, uppercase: bool) -> Cow<'_, str> {
if uppercase {
Cow::Owned(s.to_uppercase()) // allocates
} else {
Cow::Borrowed(s) // zero-cost
}
}
let input = "hello";
let result = maybe_modify(input, false);
// result is borrowed, no allocation
```
---
## Struct Design Patterns
### 1. Owned Fields vs References
```rust
// Use owned fields for most cases
struct User {
name: String,
email: String,
}
// Use references only when lifetime is clear
struct UserView<'a> {
name: &'a str,
email: &'a str,
}
// Pattern: owned data + view for efficiency
impl User {
fn view(&self) -> UserView<'_> {
UserView {
name: &self.name,
email: &self.email,
}
}
}
```
### 2. Builder Pattern with Ownership
```rust
#[derive(Default)]
struct RequestBuilder {
url: Option<String>,
method: Option<String>,
body: Option<Vec<u8>>,
}
impl RequestBuilder {
fn new() -> Self {
Self::default()
}
// Take self by value for chaining
fn url(mut self, url: impl Into<String>) -> Self {
self.url = Some(url.into());
self
}
fn method(mut self, method: impl Into<String>) -> Self {
self.method = Some(method.into());
self
}
fn build(self) -> Result<Request, Error> {
Ok(Request {
url: self.url.ok_or(Error::MissingUrl)?,
method: self.method.unwrap_or_else(|| "GET".to_string()),
body: self.body.unwrap_or_default(),
})
}
}
// Usage:
let req = RequestBuilder::new()
.url("https://example.com")
.method("POST")
.build()?;
```
### 3. Interior Mutability When Needed
```rust
use std::cell::RefCell;
use std::rc::Rc;
// Shared mutable state in single-threaded context
struct Counter {
value: Rc<RefCell<u32>>,
}
impl Counter {
fn new() -> Self {
Counter {
value: Rc::new(RefCell::new(0)),
}
}
fn increment(&self) {
*self.value.borrow_mut() += 1;
}
fn get(&self) -> u32 {
*self.value.borrow()
}
fn clone_handle(&self) -> Self {
Counter {
value: Rc::clone(&self.value),
}
}
}
```
---
## Collection Patterns
### 1. Efficient Iteration
```rust
let items = vec![1, 2, 3, 4, 5];
// Iterate by reference (no move)
for item in &items {
println!("{}", item);
}
// Iterate by mutable reference
for item in &mut items.clone() {
*item *= 2;
}
// Consume with into_iter when done
let sum: i32 = items.into_iter().sum();
```
### 2. Collecting Results
```rust
// Collect into owned collection
let strings: Vec<String> = (0..5)
.map(|i| format!("item_{}", i))
.collect();
// Collect references
let refs: Vec<&str> = strings.iter().map(|s| s.as_str()).collect();
// Collect with transformation
let result: Result<Vec<i32>, _> = ["1", "2", "3"]
.iter()
.map(|s| s.parse::<i32>())
.collect();
```
### 3. Entry API for Maps
```rust
use std::collections::HashMap;
let mut map: HashMap<String, Vec<i32>> = HashMap::new();
// Efficient: don't search twice
map.entry("key".to_string())
.or_insert_with(Vec::new)
.push(42);
// With entry modification
map.entry("key".to_string())
.and_modify(|v| v.push(43))
.or_insert_with(|| vec![43]);
```
---
## Error Handling with Ownership
### 1. Preserve Context in Errors
```rust
use std::error::Error;
use std::fmt;
#[derive(Debug)]
struct ParseError {
input: String, // owns the problematic input
message: String,
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Failed to parse '{}': {}", self.input, self.message)
}
}
fn parse(input: &str) -> Result<i32, ParseError> {
input.parse().map_err(|_| ParseError {
input: input.to_string(), // clone for error context
message: "not a valid integer".to_string(),
})
}
```
### 2. Ownership in Result Chains
```rust
fn process_data(path: &str) -> Result<ProcessedData, Error> {
let content = std::fs::read_to_string(path)?; // owned String
let parsed = parse_content(&content)?; // borrow
let processed = transform(parsed)?; // ownership moves
Ok(processed) // return owned
}
```
---
## Performance Considerations
### 1. Avoid Unnecessary Clones
```rust
// BAD: cloning just to compare
fn contains_item(items: &[String], target: &str) -> bool {
items.iter().any(|s| s.clone() == target) // unnecessary clone
}
// GOOD: compare references
fn contains_item(items: &[String], target: &str) -> bool {
items.iter().any(|s| s == target) // String implements PartialEq<str>
}
```
### 2. Use Slices for Flexibility
```rust
// BAD: requires Vec
fn sum(numbers: &Vec<i32>) -> i32 {
numbers.iter().sum()
}
// GOOD: accepts any slice
fn sum(numbers: &[i32]) -> i32 {
numbers.iter().sum()
}
// Now works with:
sum(&vec![1, 2, 3]); // Vec
sum(&[1, 2, 3]); // array
sum(&array[1..3]); // slice
```
### 3. In-Place Mutation
```rust
// BAD: allocates new String
fn make_uppercase(s: &str) -> String {
s.to_uppercase()
}
// GOOD when you own the data: mutate in place
fn make_uppercase(mut s: String) -> String {
s.make_ascii_uppercase(); // in-place for ASCII
s
}
```

View File

@@ -1,265 +0,0 @@
# Common Ownership Errors & Fixes
## E0382: Use of Moved Value
### Error Pattern
```rust
let s = String::from("hello");
let s2 = s; // s moved here
println!("{}", s); // ERROR: value borrowed after move
```
### Fix Options
**Option 1: Clone (if ownership not needed)**
```rust
let s = String::from("hello");
let s2 = s.clone(); // s is cloned
println!("{}", s); // OK: s still valid
```
**Option 2: Borrow (if modification not needed)**
```rust
let s = String::from("hello");
let s2 = &s; // borrow, not move
println!("{}", s); // OK
println!("{}", s2); // OK
```
**Option 3: Use Rc/Arc (for shared ownership)**
```rust
use std::rc::Rc;
let s = Rc::new(String::from("hello"));
let s2 = Rc::clone(&s); // shared ownership
println!("{}", s); // OK
println!("{}", s2); // OK
```
---
## E0597: Borrowed Value Does Not Live Long Enough
### Error Pattern
```rust
fn get_str() -> &str {
let s = String::from("hello");
&s // ERROR: s dropped here, but reference returned
}
```
### Fix Options
**Option 1: Return owned value**
```rust
fn get_str() -> String {
String::from("hello") // return owned value
}
```
**Option 2: Use 'static lifetime**
```rust
fn get_str() -> &'static str {
"hello" // string literal has 'static lifetime
}
```
**Option 3: Accept reference parameter**
```rust
fn get_str<'a>(s: &'a str) -> &'a str {
s // return reference with same lifetime as input
}
```
---
## E0499: Cannot Borrow as Mutable More Than Once
### Error Pattern
```rust
let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s; // ERROR: second mutable borrow
println!("{}, {}", r1, r2);
```
### Fix Options
**Option 1: Sequential borrows**
```rust
let mut s = String::from("hello");
{
let r1 = &mut s;
r1.push_str(" world");
} // r1 goes out of scope
let r2 = &mut s; // OK: r1 no longer exists
```
**Option 2: Use RefCell for interior mutability**
```rust
use std::cell::RefCell;
let s = RefCell::new(String::from("hello"));
let mut r1 = s.borrow_mut();
// drop r1 before borrowing again
drop(r1);
let mut r2 = s.borrow_mut();
```
---
## E0502: Cannot Borrow as Mutable While Immutable Borrow Exists
### Error Pattern
```rust
let mut v = vec![1, 2, 3];
let first = &v[0]; // immutable borrow
v.push(4); // ERROR: mutable borrow while immutable exists
println!("{}", first);
```
### Fix Options
**Option 1: Finish using immutable borrow first**
```rust
let mut v = vec![1, 2, 3];
let first = v[0]; // copy value, not borrow
v.push(4); // OK
println!("{}", first); // OK: using copied value
```
**Option 2: Clone before mutating**
```rust
let mut v = vec![1, 2, 3];
let first = v[0].clone(); // if T: Clone
v.push(4);
println!("{}", first);
```
---
## E0507: Cannot Move Out of Borrowed Content
### Error Pattern
```rust
fn take_string(s: &String) {
let moved = *s; // ERROR: cannot move out of borrowed content
}
```
### Fix Options
**Option 1: Clone**
```rust
fn take_string(s: &String) {
let cloned = s.clone();
}
```
**Option 2: Take ownership in function signature**
```rust
fn take_string(s: String) { // take ownership
let moved = s;
}
```
**Option 3: Use mem::take for Option/Default types**
```rust
fn take_from_option(opt: &mut Option<String>) -> Option<String> {
std::mem::take(opt) // replaces with None, returns owned value
}
```
---
## E0515: Return Local Reference
### Error Pattern
```rust
fn create_string() -> &String {
let s = String::from("hello");
&s // ERROR: cannot return reference to local variable
}
```
### Fix Options
**Option 1: Return owned value**
```rust
fn create_string() -> String {
String::from("hello")
}
```
**Option 2: Use static/const**
```rust
fn get_static_str() -> &'static str {
"hello"
}
```
---
## E0716: Temporary Value Dropped While Borrowed
### Error Pattern
```rust
let r: &str = &String::from("hello"); // ERROR: temporary dropped
println!("{}", r);
```
### Fix Options
**Option 1: Bind to variable first**
```rust
let s = String::from("hello");
let r: &str = &s;
println!("{}", r);
```
**Option 2: Use let binding with reference**
```rust
let r: &str = {
let s = String::from("hello");
// s.as_str() // ERROR: still temporary
Box::leak(s.into_boxed_str()) // extreme: leak for 'static
};
```
---
## Pattern: Loop Ownership Issues
### Error Pattern
```rust
let strings = vec![String::from("a"), String::from("b")];
for s in strings {
println!("{}", s);
}
// ERROR: strings moved into loop
println!("{:?}", strings);
```
### Fix Options
**Option 1: Iterate by reference**
```rust
let strings = vec![String::from("a"), String::from("b")];
for s in &strings {
println!("{}", s);
}
println!("{:?}", strings); // OK
```
**Option 2: Use iter()**
```rust
for s in strings.iter() {
println!("{}", s);
}
```
**Option 3: Clone if needed**
```rust
for s in strings.clone() {
// consumes cloned vec
}
println!("{:?}", strings); // original still available
```

View File

@@ -1,229 +0,0 @@
# Lifetime Patterns
## Basic Lifetime Annotation
### When Required
```rust
// ERROR: missing lifetime specifier
fn longest(x: &str, y: &str) -> &str {
if x.len() > y.len() { x } else { y }
}
// FIX: explicit lifetime
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
```
### Lifetime Elision Rules
1. Each input reference gets its own lifetime
2. If one input lifetime, output uses same
3. If `&self` or `&mut self`, output uses self's lifetime
```rust
// These are equivalent (elision applies):
fn first_word(s: &str) -> &str { ... }
fn first_word<'a>(s: &'a str) -> &'a str { ... }
// Method with self (elision applies):
impl MyStruct {
fn get_ref(&self) -> &str { ... }
// Equivalent to:
fn get_ref<'a>(&'a self) -> &'a str { ... }
}
```
---
## Struct Lifetimes
### Struct Holding References
```rust
// Struct must declare lifetime for references
struct Excerpt<'a> {
part: &'a str,
}
impl<'a> Excerpt<'a> {
fn level(&self) -> i32 { 3 }
// Return reference tied to self's lifetime
fn get_part(&self) -> &str {
self.part
}
}
```
### Multiple Lifetimes in Struct
```rust
struct Multi<'a, 'b> {
x: &'a str,
y: &'b str,
}
// Use when references may have different lifetimes
fn make_multi<'a, 'b>(x: &'a str, y: &'b str) -> Multi<'a, 'b> {
Multi { x, y }
}
```
---
## 'static Lifetime
### When to Use
```rust
// String literals are 'static
let s: &'static str = "hello";
// Owned data can be leaked to 'static
let leaked: &'static str = Box::leak(String::from("hello").into_boxed_str());
// Thread spawn requires 'static or move
std::thread::spawn(move || {
// closure owns data, satisfies 'static
});
```
### Avoid Overusing 'static
```rust
// BAD: requires 'static unnecessarily
fn process(s: &'static str) { ... }
// GOOD: use generic lifetime
fn process<'a>(s: &'a str) { ... }
// or
fn process(s: &str) { ... } // lifetime elision
```
---
## Higher-Ranked Trait Bounds (HRTB)
### for<'a> Syntax
```rust
// Function that works with any lifetime
fn apply_to_ref<F>(f: F)
where
F: for<'a> Fn(&'a str) -> &'a str,
{
let s = String::from("hello");
let result = f(&s);
println!("{}", result);
}
```
### Common Use: Closure Bounds
```rust
// Closure that borrows any lifetime
fn filter_refs<F>(items: &[&str], pred: F) -> Vec<&str>
where
F: for<'a> Fn(&'a str) -> bool,
{
items.iter().copied().filter(|s| pred(s)).collect()
}
```
---
## Lifetime Bounds
### 'a: 'b (Outlives)
```rust
// 'a must live at least as long as 'b
fn coerce<'a, 'b>(x: &'a str) -> &'b str
where
'a: 'b,
{
x
}
```
### T: 'a (Type Outlives Lifetime)
```rust
// T must live at least as long as 'a
struct Wrapper<'a, T: 'a> {
value: &'a T,
}
// Common pattern with trait objects
fn use_trait<'a, T: MyTrait + 'a>(t: &'a T) { ... }
```
---
## Common Lifetime Mistakes
### Mistake 1: Returning Reference to Local
```rust
// WRONG
fn dangle() -> &String {
let s = String::from("hello");
&s // s dropped, reference invalid
}
// RIGHT
fn no_dangle() -> String {
String::from("hello")
}
```
### Mistake 2: Conflicting Lifetimes
```rust
// WRONG: might return reference to y which has shorter lifetime
fn wrong<'a, 'b>(x: &'a str, y: &'b str) -> &'a str {
y // ERROR: 'b might not live as long as 'a
}
// RIGHT: use same lifetime or add bound
fn right<'a>(x: &'a str, y: &'a str) -> &'a str {
y // OK: both have lifetime 'a
}
```
### Mistake 3: Struct Outlives Reference
```rust
// WRONG: s might outlive the string it references
let r;
{
let s = String::from("hello");
r = Excerpt { part: &s }; // ERROR
}
println!("{}", r.part); // s already dropped
// RIGHT: ensure source outlives struct
let s = String::from("hello");
let r = Excerpt { part: &s };
println!("{}", r.part); // OK: s still in scope
```
---
## Subtyping and Variance
### Covariance
```rust
// &'a T is covariant in 'a
// Can use &'long where &'short expected
fn example<'short, 'long: 'short>(long_ref: &'long str) {
let short_ref: &'short str = long_ref; // OK: covariance
}
```
### Invariance
```rust
// &'a mut T is invariant in 'a
fn example<'a, 'b>(x: &'a mut &'b str, y: &'b str) {
*x = y; // ERROR if 'a and 'b are different
}
```
### Practical Impact
```rust
// This works due to covariance
fn accept_any<'a>(s: &'a str) { ... }
let s = String::from("hello");
let long_lived: &str = &s;
accept_any(long_lived); // 'long coerces to 'short
```

View File

@@ -1,158 +0,0 @@
---
name: m02-resource
description: "CRITICAL: Use for smart pointers and resource management. Triggers: Box, Rc, Arc, Weak, RefCell, Cell, smart pointer, heap allocation, reference counting, RAII, Drop, should I use Box or Rc, when to use Arc vs Rc, 智能指针, 引用计数, 堆分配"
---
# Resource Management
> **Layer 1: Language Mechanics**
## Core Question
**What ownership pattern does this resource need?**
Before choosing a smart pointer, understand:
- Is ownership single or shared?
- Is access single-threaded or multi-threaded?
- Are there potential cycles?
---
## Error → Design Question
| Error | Don't Just Say | Ask Instead |
|-------|----------------|-------------|
| "Need heap allocation" | "Use Box" | Why can't this be on stack? |
| Rc memory leak | "Use Weak" | Is the cycle necessary in design? |
| RefCell panic | "Use try_borrow" | Is runtime check the right approach? |
| Arc overhead complaint | "Accept it" | Is multi-thread access actually needed? |
---
## Thinking Prompt
Before choosing a smart pointer:
1. **What's the ownership model?**
- Single owner → Box or owned value
- Shared ownership → Rc/Arc
- Weak reference → Weak
2. **What's the thread context?**
- Single-thread → Rc, Cell, RefCell
- Multi-thread → Arc, Mutex, RwLock
3. **Are there cycles?**
- Yes → One direction must be Weak
- No → Regular Rc/Arc is fine
---
## Trace Up ↑
When pointer choice is unclear, trace to design:
```
"Should I use Arc or Rc?"
↑ Ask: Is this data shared across threads?
↑ Check: m07-concurrency (thread model)
↑ Check: domain-* (performance constraints)
```
| Situation | Trace To | Question |
|-----------|----------|----------|
| Rc vs Arc confusion | m07-concurrency | What's the concurrency model? |
| RefCell panics | m03-mutability | Is interior mutability right here? |
| Memory leaks | m12-lifecycle | Where should cleanup happen? |
---
## Trace Down ↓
From design to implementation:
```
"Need single-owner heap data"
↓ Use: Box<T>
"Need shared immutable data (single-thread)"
↓ Use: Rc<T>
"Need shared immutable data (multi-thread)"
↓ Use: Arc<T>
"Need to break reference cycle"
↓ Use: Weak<T>
"Need shared mutable data"
↓ Single-thread: Rc<RefCell<T>>
↓ Multi-thread: Arc<Mutex<T>> or Arc<RwLock<T>>
```
---
## Quick Reference
| Type | Ownership | Thread-Safe | Use When |
|------|-----------|-------------|----------|
| `Box<T>` | Single | Yes | Heap allocation, recursive types |
| `Rc<T>` | Shared | No | Single-thread shared ownership |
| `Arc<T>` | Shared | Yes | Multi-thread shared ownership |
| `Weak<T>` | Weak ref | Same as Rc/Arc | Break reference cycles |
| `Cell<T>` | Single | No | Interior mutability (Copy types) |
| `RefCell<T>` | Single | No | Interior mutability (runtime check) |
## Decision Flowchart
```
Need heap allocation?
├─ Yes → Single owner?
│ ├─ Yes → Box<T>
│ └─ No → Multi-thread?
│ ├─ Yes → Arc<T>
│ └─ No → Rc<T>
└─ No → Stack allocation (default)
Have reference cycles?
├─ Yes → Use Weak for one direction
└─ No → Regular Rc/Arc
Need interior mutability?
├─ Yes → Thread-safe needed?
│ ├─ Yes → Mutex<T> or RwLock<T>
│ └─ No → T: Copy? → Cell<T> : RefCell<T>
└─ No → Use &mut T
```
---
## Common Errors
| Problem | Cause | Fix |
|---------|-------|-----|
| Rc cycle leak | Mutual strong refs | Use Weak for one direction |
| RefCell panic | Borrow conflict at runtime | Use try_borrow or restructure |
| Arc overhead | Atomic ops in hot path | Consider Rc if single-threaded |
| Box unnecessary | Data fits on stack | Remove Box |
---
## Anti-Patterns
| Anti-Pattern | Why Bad | Better |
|--------------|---------|--------|
| Arc everywhere | Unnecessary atomic overhead | Use Rc for single-thread |
| RefCell everywhere | Runtime panics | Design clear ownership |
| Box for small types | Unnecessary allocation | Stack allocation |
| Ignore Weak for cycles | Memory leaks | Design parent-child with Weak |
---
## Related Skills
| When | See |
|------|-----|
| Ownership errors | m01-ownership |
| Interior mutability details | m03-mutability |
| Multi-thread context | m07-concurrency |
| Resource lifecycle | m12-lifecycle |

View File

@@ -1,152 +0,0 @@
---
name: m03-mutability
description: "CRITICAL: Use for mutability issues. Triggers: E0596, E0499, E0502, cannot borrow as mutable, already borrowed as immutable, mut, &mut, interior mutability, Cell, RefCell, Mutex, RwLock, 可变性, 内部可变性, 借用冲突"
---
# Mutability
> **Layer 1: Language Mechanics**
## Core Question
**Why does this data need to change, and who can change it?**
Before adding interior mutability, understand:
- Is mutation essential or accidental complexity?
- Who should control mutation?
- Is the mutation pattern safe?
---
## Error → Design Question
| Error | Don't Just Say | Ask Instead |
|-------|----------------|-------------|
| E0596 | "Add mut" | Should this really be mutable? |
| E0499 | "Split borrows" | Is the data structure right? |
| E0502 | "Separate scopes" | Why do we need both borrows? |
| RefCell panic | "Use try_borrow" | Is runtime check appropriate? |
---
## Thinking Prompt
Before adding mutability:
1. **Is mutation necessary?**
- Maybe transform → return new value
- Maybe builder → construct immutably
2. **Who controls mutation?**
- External caller → `&mut T`
- Internal logic → interior mutability
- Concurrent access → synchronized mutability
3. **What's the thread context?**
- Single-thread → Cell/RefCell
- Multi-thread → Mutex/RwLock/Atomic
---
## Trace Up ↑
When mutability conflicts persist:
```
E0499/E0502 (borrow conflicts)
↑ Ask: Is the data structure designed correctly?
↑ Check: m09-domain (should data be split?)
↑ Check: m07-concurrency (is async involved?)
```
| Persistent Error | Trace To | Question |
|-----------------|----------|----------|
| Repeated borrow conflicts | m09-domain | Should data be restructured? |
| RefCell in async | m07-concurrency | Is Send/Sync needed? |
| Mutex deadlocks | m07-concurrency | Is the lock design right? |
---
## Trace Down ↓
From design to implementation:
```
"Need mutable access from &self"
↓ T: Copy → Cell<T>
↓ T: !Copy → RefCell<T>
"Need thread-safe mutation"
↓ Simple counters → AtomicXxx
↓ Complex data → Mutex<T> or RwLock<T>
"Need shared mutable state"
↓ Single-thread: Rc<RefCell<T>>
↓ Multi-thread: Arc<Mutex<T>>
```
---
## Borrow Rules
```
At any time, you can have EITHER:
├─ Multiple &T (immutable borrows)
└─ OR one &mut T (mutable borrow)
Never both simultaneously.
```
## Quick Reference
| Pattern | Thread-Safe | Runtime Cost | Use When |
|---------|-------------|--------------|----------|
| `&mut T` | N/A | Zero | Exclusive mutable access |
| `Cell<T>` | No | Zero | Copy types, no refs needed |
| `RefCell<T>` | No | Runtime check | Non-Copy, need runtime borrow |
| `Mutex<T>` | Yes | Lock contention | Thread-safe mutation |
| `RwLock<T>` | Yes | Lock contention | Many readers, few writers |
| `Atomic*` | Yes | Minimal | Simple types (bool, usize) |
## Error Code Reference
| Error | Cause | Quick Fix |
|-------|-------|-----------|
| E0596 | Borrowing immutable as mutable | Add `mut` or redesign |
| E0499 | Multiple mutable borrows | Restructure code flow |
| E0502 | &mut while & exists | Separate borrow scopes |
---
## Interior Mutability Decision
| Scenario | Choose |
|----------|--------|
| T: Copy, single-thread | `Cell<T>` |
| T: !Copy, single-thread | `RefCell<T>` |
| T: Copy, multi-thread | `AtomicXxx` |
| T: !Copy, multi-thread | `Mutex<T>` or `RwLock<T>` |
| Read-heavy, multi-thread | `RwLock<T>` |
| Simple flags/counters | `AtomicBool`, `AtomicUsize` |
---
## Anti-Patterns
| Anti-Pattern | Why Bad | Better |
|--------------|---------|--------|
| RefCell everywhere | Runtime panics | Clear ownership design |
| Mutex for single-thread | Unnecessary overhead | RefCell |
| Ignore RefCell panic | Hard to debug | Handle or restructure |
| Lock inside hot loop | Performance killer | Batch operations |
---
## Related Skills
| When | See |
|------|-----|
| Smart pointer choice | m02-resource |
| Thread safety | m07-concurrency |
| Data structure design | m09-domain |
| Anti-patterns | m15-anti-pattern |

View File

@@ -1,164 +0,0 @@
---
name: m04-zero-cost
description: "CRITICAL: Use for generics, traits, zero-cost abstraction. Triggers: E0277, E0308, E0599, generic, trait, impl, dyn, where, monomorphization, static dispatch, dynamic dispatch, impl Trait, trait bound not satisfied, 泛型, 特征, 零成本抽象, 单态化"
---
# Zero-Cost Abstraction
> **Layer 1: Language Mechanics**
## Core Question
**Do we need compile-time or runtime polymorphism?**
Before choosing between generics and trait objects:
- Is the type known at compile time?
- Is a heterogeneous collection needed?
- What's the performance priority?
---
## Error → Design Question
| Error | Don't Just Say | Ask Instead |
|-------|----------------|-------------|
| E0277 | "Add trait bound" | Is this abstraction at the right level? |
| E0308 | "Fix the type" | Should types be unified or distinct? |
| E0599 | "Import the trait" | Is the trait the right abstraction? |
| E0038 | "Make object-safe" | Do we really need dynamic dispatch? |
---
## Thinking Prompt
Before adding trait bounds:
1. **What abstraction is needed?**
- Same behavior, different types → trait
- Different behavior, same type → enum
- No abstraction needed → concrete type
2. **When is type known?**
- Compile time → generics (static dispatch)
- Runtime → trait objects (dynamic dispatch)
3. **What's the trade-off priority?**
- Performance → generics
- Compile time → trait objects
- Flexibility → depends
---
## Trace Up ↑
When type system fights back:
```
E0277 (trait bound not satisfied)
↑ Ask: Is the abstraction level correct?
↑ Check: m09-domain (what behavior is being abstracted?)
↑ Check: m05-type-driven (should use newtype?)
```
| Persistent Error | Trace To | Question |
|-----------------|----------|----------|
| Complex trait bounds | m09-domain | Is the abstraction right? |
| Object safety issues | m05-type-driven | Can typestate help? |
| Type explosion | m10-performance | Accept dyn overhead? |
---
## Trace Down ↓
From design to implementation:
```
"Need to abstract over types with same behavior"
↓ Types known at compile time → impl Trait or generics
↓ Types determined at runtime → dyn Trait
"Need collection of different types"
↓ Closed set → enum
↓ Open set → Vec<Box<dyn Trait>>
"Need to return different types"
↓ Same type → impl Trait
↓ Different types → Box<dyn Trait>
```
---
## Quick Reference
| Pattern | Dispatch | Code Size | Runtime Cost |
|---------|----------|-----------|--------------|
| `fn foo<T: Trait>()` | Static | +bloat | Zero |
| `fn foo(x: &dyn Trait)` | Dynamic | Minimal | vtable lookup |
| `impl Trait` return | Static | +bloat | Zero |
| `Box<dyn Trait>` | Dynamic | Minimal | Allocation + vtable |
## Syntax Comparison
```rust
// Static dispatch - type known at compile time
fn process(x: impl Display) { } // argument position
fn process<T: Display>(x: T) { } // explicit generic
fn get() -> impl Display { } // return position
// Dynamic dispatch - type determined at runtime
fn process(x: &dyn Display) { } // reference
fn process(x: Box<dyn Display>) { } // owned
```
## Error Code Reference
| Error | Cause | Quick Fix |
|-------|-------|-----------|
| E0277 | Type doesn't impl trait | Add impl or change bound |
| E0308 | Type mismatch | Check generic params |
| E0599 | No method found | Import trait with `use` |
| E0038 | Trait not object-safe | Use generics or redesign |
---
## Decision Guide
| Scenario | Choose | Why |
|----------|--------|-----|
| Performance critical | Generics | Zero runtime cost |
| Heterogeneous collection | `dyn Trait` | Different types at runtime |
| Plugin architecture | `dyn Trait` | Unknown types at compile |
| Reduce compile time | `dyn Trait` | Less monomorphization |
| Small, known type set | `enum` | No indirection |
---
## Object Safety
A trait is object-safe if it:
- Doesn't have `Self: Sized` bound
- Doesn't return `Self`
- Doesn't have generic methods
- Uses `where Self: Sized` for non-object-safe methods
---
## Anti-Patterns
| Anti-Pattern | Why Bad | Better |
|--------------|---------|--------|
| Over-generic everything | Compile time, complexity | Concrete types when possible |
| `dyn` for known types | Unnecessary indirection | Generics |
| Complex trait hierarchies | Hard to understand | Simpler design |
| Ignore object safety | Limits flexibility | Plan for dyn if needed |
---
## Related Skills
| When | See |
|------|-----|
| Type-driven design | m05-type-driven |
| Domain abstraction | m09-domain |
| Performance concerns | m10-performance |
| Send/Sync bounds | m07-concurrency |

View File

@@ -1,174 +0,0 @@
---
name: m05-type-driven
description: "CRITICAL: Use for type-driven design. Triggers: type state, PhantomData, newtype, marker trait, builder pattern, make invalid states unrepresentable, compile-time validation, sealed trait, ZST, 类型状态, 新类型模式, 类型驱动设计"
---
# Type-Driven Design
> **Layer 1: Language Mechanics**
## Core Question
**How can the type system prevent invalid states?**
Before reaching for runtime checks:
- Can the compiler catch this error?
- Can invalid states be unrepresentable?
- Can the type encode the invariant?
---
## Error → Design Question
| Pattern | Don't Just Say | Ask Instead |
|---------|----------------|-------------|
| Primitive obsession | "It's just a string" | What does this value represent? |
| Boolean flags | "Add an is_valid flag" | Can states be types? |
| Optional everywhere | "Check for None" | Is absence really possible? |
| Validation at runtime | "Return Err if invalid" | Can we validate at construction? |
---
## Thinking Prompt
Before adding runtime validation:
1. **Can the type encode the constraint?**
- Numeric range → bounded types or newtypes
- Valid states → type state pattern
- Semantic meaning → newtype
2. **When is validation possible?**
- At construction → validated newtype
- At state transition → type state
- Only at runtime → Result with clear error
3. **Who needs to know the invariant?**
- Compiler → type-level encoding
- API users → clear type signatures
- Runtime only → documentation
---
## Trace Up ↑
When type design is unclear:
```
"Need to validate email format"
↑ Ask: Is this a domain value object?
↑ Check: m09-domain (Email as Value Object)
↑ Check: domain-* (validation requirements)
```
| Situation | Trace To | Question |
|-----------|----------|----------|
| What types to create | m09-domain | What's the domain model? |
| State machine design | m09-domain | What are valid transitions? |
| Marker trait usage | m04-zero-cost | Static or dynamic dispatch? |
---
## Trace Down ↓
From design to implementation:
```
"Need type-safe wrapper for primitives"
↓ Newtype: struct UserId(u64);
"Need compile-time state validation"
↓ Type State: Connection<Connected>
"Need to track phantom type parameters"
↓ PhantomData: PhantomData<T>
"Need capability markers"
↓ Marker Trait: trait Validated {}
"Need gradual construction"
↓ Builder: Builder::new().field(x).build()
```
---
## Quick Reference
| Pattern | Purpose | Example |
|---------|---------|---------|
| Newtype | Type safety | `struct UserId(u64);` |
| Type State | State machine | `Connection<Connected>` |
| PhantomData | Variance/lifetime | `PhantomData<&'a T>` |
| Marker Trait | Capability flag | `trait Validated {}` |
| Builder | Gradual construction | `Builder::new().name("x").build()` |
| Sealed Trait | Prevent external impl | `mod private { pub trait Sealed {} }` |
## Pattern Examples
### Newtype
```rust
struct Email(String); // Not just any string
impl Email {
pub fn new(s: &str) -> Result<Self, ValidationError> {
// Validate once, trust forever
validate_email(s)?;
Ok(Self(s.to_string()))
}
}
```
### Type State
```rust
struct Connection<State>(TcpStream, PhantomData<State>);
struct Disconnected;
struct Connected;
struct Authenticated;
impl Connection<Disconnected> {
fn connect(self) -> Connection<Connected> { ... }
}
impl Connection<Connected> {
fn authenticate(self) -> Connection<Authenticated> { ... }
}
```
---
## Decision Guide
| Need | Pattern |
|------|---------|
| Type safety for primitives | Newtype |
| Compile-time state validation | Type State |
| Lifetime/variance markers | PhantomData |
| Capability flags | Marker Trait |
| Gradual construction | Builder |
| Closed set of impls | Sealed Trait |
| Zero-sized type marker | ZST struct |
---
## Anti-Patterns
| Anti-Pattern | Why Bad | Better |
|--------------|---------|--------|
| Boolean flags for states | Runtime errors | Type state |
| String for semantic types | No type safety | Newtype |
| Option for uninitialized | Unclear invariant | Builder |
| Public fields with invariants | Invariant violation | Private + validated new() |
---
## Related Skills
| When | See |
|------|-----|
| Domain modeling | m09-domain |
| Trait design | m04-zero-cost |
| Error handling in constructors | m06-error-handling |
| Anti-patterns | m15-anti-pattern |

View File

@@ -1,165 +0,0 @@
---
name: m06-error-handling
description: "CRITICAL: Use for error handling. Triggers: Result, Option, Error, ?, unwrap, expect, panic, anyhow, thiserror, when to panic vs return Result, custom error, error propagation, 错误处理, Result 用法, 什么时候用 panic"
---
# Error Handling
> **Layer 1: Language Mechanics**
## Core Question
**Is this failure expected or a bug?**
Before choosing error handling strategy:
- Can this fail in normal operation?
- Who should handle this failure?
- What context does the caller need?
---
## Error → Design Question
| Pattern | Don't Just Say | Ask Instead |
|---------|----------------|-------------|
| unwrap panics | "Use ?" | Is None/Err actually possible here? |
| Type mismatch on ? | "Use anyhow" | Are error types designed correctly? |
| Lost error context | "Add .context()" | What does the caller need to know? |
| Too many error variants | "Use Box<dyn Error>" | Is error granularity right? |
---
## Thinking Prompt
Before handling an error:
1. **What kind of failure is this?**
- Expected → Result<T, E>
- Absence normal → Option<T>
- Bug/invariant → panic!
- Unrecoverable → panic!
2. **Who handles this?**
- Caller → propagate with ?
- Current function → match/if-let
- User → friendly error message
- Programmer → panic with message
3. **What context is needed?**
- Type of error → thiserror variants
- Call chain → anyhow::Context
- Debug info → anyhow or tracing
---
## Trace Up ↑
When error strategy is unclear:
```
"Should I return Result or Option?"
↑ Ask: Is absence/failure normal or exceptional?
↑ Check: m09-domain (what does domain say?)
↑ Check: domain-* (error handling requirements)
```
| Situation | Trace To | Question |
|-----------|----------|----------|
| Too many unwraps | m09-domain | Is the data model right? |
| Error context design | m13-domain-error | What recovery is needed? |
| Library vs app errors | m11-ecosystem | Who are the consumers? |
---
## Trace Down ↓
From design to implementation:
```
"Expected failure, library code"
↓ Use: thiserror for typed errors
"Expected failure, application code"
↓ Use: anyhow for ergonomic errors
"Absence is normal (find, get, lookup)"
↓ Use: Option<T>
"Bug or invariant violation"
↓ Use: panic!, assert!, unreachable!
"Need to propagate with context"
↓ Use: .context("what was happening")
```
---
## Quick Reference
| Pattern | When | Example |
|---------|------|---------|
| `Result<T, E>` | Recoverable error | `fn read() -> Result<String, io::Error>` |
| `Option<T>` | Absence is normal | `fn find() -> Option<&Item>` |
| `?` | Propagate error | `let data = file.read()?;` |
| `unwrap()` | Dev/test only | `config.get("key").unwrap()` |
| `expect()` | Invariant holds | `env.get("HOME").expect("HOME set")` |
| `panic!` | Unrecoverable | `panic!("critical failure")` |
## Library vs Application
| Context | Error Crate | Why |
|---------|-------------|-----|
| Library | `thiserror` | Typed errors for consumers |
| Application | `anyhow` | Ergonomic error handling |
| Mixed | Both | thiserror at boundaries, anyhow internally |
## Decision Flowchart
```
Is failure expected?
├─ Yes → Is absence the only "failure"?
│ ├─ Yes → Option<T>
│ └─ No → Result<T, E>
│ ├─ Library → thiserror
│ └─ Application → anyhow
└─ No → Is it a bug?
├─ Yes → panic!, assert!
└─ No → Consider if really unrecoverable
Use ? → Need context?
├─ Yes → .context("message")
└─ No → Plain ?
```
---
## Common Errors
| Error | Cause | Fix |
|-------|-------|-----|
| `unwrap()` panic | Unhandled None/Err | Use `?` or match |
| Type mismatch | Different error types | Use `anyhow` or `From` |
| Lost context | `?` without context | Add `.context()` |
| `cannot use ?` | Missing Result return | Return `Result<(), E>` |
---
## Anti-Patterns
| Anti-Pattern | Why Bad | Better |
|--------------|---------|--------|
| `.unwrap()` everywhere | Panics in production | `.expect("reason")` or `?` |
| Ignore errors silently | Bugs hidden | Handle or propagate |
| `panic!` for expected errors | Bad UX, no recovery | Result |
| Box<dyn Error> everywhere | Lost type info | thiserror |
---
## Related Skills
| When | See |
|------|-----|
| Domain error strategy | m13-domain-error |
| Crate boundaries | m11-ecosystem |
| Type-safe errors | m05-type-driven |
| Mental models | m14-mental-model |

View File

@@ -1,332 +0,0 @@
# Error Handling: Library vs Application
## Library Error Design
### Principles
1. **Define specific error types** - Don't use `anyhow` in libraries
2. **Implement std::error::Error** - For compatibility
3. **Provide error variants** - Let users match on errors
4. **Include source errors** - Enable error chains
5. **Be `Send + Sync`** - For async compatibility
### Example: Library Error Type
```rust
// lib.rs
use thiserror::Error;
#[derive(Error, Debug)]
pub enum DatabaseError {
#[error("connection failed: {host}:{port}")]
ConnectionFailed {
host: String,
port: u16,
#[source]
source: std::io::Error,
},
#[error("query failed: {query}")]
QueryFailed {
query: String,
#[source]
source: SqlError,
},
#[error("record not found: {table}.{id}")]
NotFound { table: String, id: String },
#[error("constraint violation: {0}")]
ConstraintViolation(String),
}
// Public Result alias
pub type Result<T> = std::result::Result<T, DatabaseError>;
// Library functions
pub fn connect(host: &str, port: u16) -> Result<Connection> {
// ...
}
pub fn query(conn: &Connection, sql: &str) -> Result<Rows> {
// ...
}
```
### Library Usage of Errors
```rust
impl Database {
pub fn get_user(&self, id: &str) -> Result<User> {
let rows = self.query(&format!("SELECT * FROM users WHERE id = '{}'", id))?;
rows.first()
.cloned()
.ok_or_else(|| DatabaseError::NotFound {
table: "users".to_string(),
id: id.to_string(),
})
}
}
```
---
## Application Error Design
### Principles
1. **Use anyhow for convenience** - Or custom unified error
2. **Add context liberally** - Help debugging
3. **Log at boundaries** - Don't log in libraries
4. **Convert to user-friendly messages** - For display
### Example: Application Error Handling
```rust
// main.rs
use anyhow::{Context, Result};
use tracing::{error, info};
async fn run_server() -> Result<()> {
let config = load_config()
.context("failed to load configuration")?;
let db = Database::connect(&config.db_url)
.await
.context("failed to connect to database")?;
let server = Server::new(config.port)
.context("failed to create server")?;
info!("Server starting on port {}", config.port);
server.run(db).await
.context("server error")?;
Ok(())
}
#[tokio::main]
async fn main() {
tracing_subscriber::init();
if let Err(e) = run_server().await {
error!("Application error: {:#}", e);
std::process::exit(1);
}
}
```
### Converting Library Errors
```rust
use mylib::DatabaseError;
async fn get_user_handler(id: &str) -> Result<Response> {
match db.get_user(id).await {
Ok(user) => Ok(Response::json(user)),
Err(DatabaseError::NotFound { .. }) => {
Ok(Response::not_found("User not found"))
}
Err(DatabaseError::ConnectionFailed { .. }) => {
error!("Database connection failed");
Ok(Response::internal_error("Service unavailable"))
}
Err(e) => {
error!("Database error: {}", e);
Err(e.into()) // Convert to anyhow::Error
}
}
}
```
---
## Error Handling Layers
```
┌─────────────────────────────────────┐
│ Application Layer │
│ - Use anyhow or unified error │
│ - Add context at boundaries │
│ - Log errors │
│ - Convert to user messages │
└─────────────────────────────────────┘
│ calls
┌─────────────────────────────────────┐
│ Service Layer │
│ - Map between error types │
│ - Add business context │
│ - Handle recoverable errors │
└─────────────────────────────────────┘
│ calls
┌─────────────────────────────────────┐
│ Library Layer │
│ - Define specific error types │
│ - Use thiserror │
│ - Include source errors │
│ - No logging │
└─────────────────────────────────────┘
```
---
## Practical Examples
### HTTP API Error Response
```rust
use axum::{response::IntoResponse, http::StatusCode};
use serde::Serialize;
#[derive(Serialize)]
struct ErrorResponse {
error: String,
code: String,
}
enum AppError {
NotFound(String),
BadRequest(String),
Internal(anyhow::Error),
}
impl IntoResponse for AppError {
fn into_response(self) -> axum::response::Response {
let (status, error, code) = match self {
AppError::NotFound(msg) => {
(StatusCode::NOT_FOUND, msg, "NOT_FOUND")
}
AppError::BadRequest(msg) => {
(StatusCode::BAD_REQUEST, msg, "BAD_REQUEST")
}
AppError::Internal(e) => {
tracing::error!("Internal error: {:#}", e);
(
StatusCode::INTERNAL_SERVER_ERROR,
"Internal server error".to_string(),
"INTERNAL_ERROR",
)
}
};
let body = ErrorResponse {
error,
code: code.to_string(),
};
(status, axum::Json(body)).into_response()
}
}
```
### CLI Error Handling
```rust
use anyhow::{Context, Result};
use clap::Parser;
#[derive(Parser)]
struct Args {
#[arg(short, long)]
config: String,
}
fn main() {
if let Err(e) = run() {
eprintln!("Error: {:#}", e);
std::process::exit(1);
}
}
fn run() -> Result<()> {
let args = Args::parse();
let config = std::fs::read_to_string(&args.config)
.context(format!("Failed to read config file: {}", args.config))?;
let parsed: Config = toml::from_str(&config)
.context("Failed to parse config file")?;
process(parsed)?;
println!("Done!");
Ok(())
}
```
---
## Testing Error Handling
### Testing Error Cases
```rust
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_not_found_error() {
let result = db.get_user("nonexistent");
assert!(matches!(
result,
Err(DatabaseError::NotFound { table, id })
if table == "users" && id == "nonexistent"
));
}
#[test]
fn test_error_message() {
let err = DatabaseError::NotFound {
table: "users".to_string(),
id: "123".to_string(),
};
assert_eq!(err.to_string(), "record not found: users.123");
}
#[test]
fn test_error_chain() {
let io_err = std::io::Error::new(
std::io::ErrorKind::ConnectionRefused,
"connection refused"
);
let err = DatabaseError::ConnectionFailed {
host: "localhost".to_string(),
port: 5432,
source: io_err,
};
// Check source is preserved
assert!(err.source().is_some());
}
}
```
### Testing with anyhow
```rust
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_with_context() -> anyhow::Result<()> {
let result = process("valid input")?;
assert_eq!(result, expected);
Ok(())
}
#[test]
fn test_error_context() {
let err = process("invalid")
.context("processing failed")
.unwrap_err();
// Check error chain contains expected text
let chain = format!("{:#}", err);
assert!(chain.contains("processing failed"));
}
}
```

View File

@@ -1,404 +0,0 @@
# Error Handling Patterns
## The ? Operator
### Basic Usage
```rust
fn read_config() -> Result<Config, io::Error> {
let content = std::fs::read_to_string("config.toml")?;
let config: Config = toml::from_str(&content)?; // needs From impl
Ok(config)
}
```
### With Different Error Types
```rust
use std::error::Error;
// Box<dyn Error> for quick prototyping
fn process() -> Result<(), Box<dyn Error>> {
let file = std::fs::read_to_string("data.txt")?;
let num: i32 = file.trim().parse()?; // different error type
Ok(())
}
```
### Custom Conversion with From
```rust
#[derive(Debug)]
enum MyError {
Io(std::io::Error),
Parse(std::num::ParseIntError),
}
impl From<std::io::Error> for MyError {
fn from(err: std::io::Error) -> Self {
MyError::Io(err)
}
}
impl From<std::num::ParseIntError> for MyError {
fn from(err: std::num::ParseIntError) -> Self {
MyError::Parse(err)
}
}
fn process() -> Result<i32, MyError> {
let content = std::fs::read_to_string("num.txt")?; // auto-converts
let num: i32 = content.trim().parse()?; // auto-converts
Ok(num)
}
```
---
## Error Type Design
### Simple Enum Error
```rust
#[derive(Debug, Clone, PartialEq)]
pub enum ConfigError {
NotFound,
InvalidFormat,
MissingField(String),
}
impl std::fmt::Display for ConfigError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ConfigError::NotFound => write!(f, "configuration file not found"),
ConfigError::InvalidFormat => write!(f, "invalid configuration format"),
ConfigError::MissingField(field) => write!(f, "missing field: {}", field),
}
}
}
impl std::error::Error for ConfigError {}
```
### Error with Source (Wrapping)
```rust
#[derive(Debug)]
pub struct AppError {
kind: AppErrorKind,
source: Option<Box<dyn std::error::Error + Send + Sync>>,
}
#[derive(Debug, Clone, Copy)]
pub enum AppErrorKind {
Config,
Database,
Network,
}
impl std::fmt::Display for AppError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.kind {
AppErrorKind::Config => write!(f, "configuration error"),
AppErrorKind::Database => write!(f, "database error"),
AppErrorKind::Network => write!(f, "network error"),
}
}
}
impl std::error::Error for AppError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.source.as_ref().map(|e| e.as_ref() as _)
}
}
```
---
## Using thiserror
### Basic Usage
```rust
use thiserror::Error;
#[derive(Error, Debug)]
pub enum DataError {
#[error("file not found: {path}")]
NotFound { path: String },
#[error("invalid data format")]
InvalidFormat,
#[error("IO error")]
Io(#[from] std::io::Error),
#[error("parse error: {0}")]
Parse(#[from] std::num::ParseIntError),
}
// Usage
fn load_data(path: &str) -> Result<Data, DataError> {
let content = std::fs::read_to_string(path)
.map_err(|_| DataError::NotFound { path: path.to_string() })?;
let num: i32 = content.trim().parse()?; // auto-converts with #[from]
Ok(Data { value: num })
}
```
### Transparent Wrapper
```rust
use thiserror::Error;
#[derive(Error, Debug)]
#[error(transparent)]
pub struct MyError(#[from] InnerError);
// Useful for newtype error wrappers
```
---
## Using anyhow
### For Applications
```rust
use anyhow::{Context, Result, bail, ensure};
fn process_file(path: &str) -> Result<Data> {
let content = std::fs::read_to_string(path)
.context("failed to read config file")?;
ensure!(!content.is_empty(), "config file is empty");
let data: Data = serde_json::from_str(&content)
.context("failed to parse JSON")?;
if data.version < 1 {
bail!("unsupported config version: {}", data.version);
}
Ok(data)
}
fn main() -> Result<()> {
let data = process_file("config.json")
.context("failed to load configuration")?;
Ok(())
}
```
### Error Chain
```rust
use anyhow::{Context, Result};
fn deep_function() -> Result<()> {
std::fs::read_to_string("missing.txt")
.context("failed to read file")?;
Ok(())
}
fn middle_function() -> Result<()> {
deep_function()
.context("failed in deep function")?;
Ok(())
}
fn top_function() -> Result<()> {
middle_function()
.context("failed in middle function")?;
Ok(())
}
// Error output shows full chain:
// Error: failed in middle function
// Caused by:
// 0: failed in deep function
// 1: failed to read file
// 2: No such file or directory (os error 2)
```
---
## Option Handling
### Converting Option to Result
```rust
fn find_user(id: u32) -> Option<User> { ... }
// Using ok_or for static error
fn get_user(id: u32) -> Result<User, &'static str> {
find_user(id).ok_or("user not found")
}
// Using ok_or_else for dynamic error
fn get_user(id: u32) -> Result<User, String> {
find_user(id).ok_or_else(|| format!("user {} not found", id))
}
```
### Chaining Options
```rust
fn get_nested_value(data: &Data) -> Option<&str> {
data.config
.as_ref()?
.nested
.as_ref()?
.value
.as_deref()
}
// Equivalent with and_then
fn get_nested_value(data: &Data) -> Option<&str> {
data.config
.as_ref()
.and_then(|c| c.nested.as_ref())
.and_then(|n| n.value.as_deref())
}
```
---
## Pattern: Result Combinators
### map and map_err
```rust
fn parse_port(s: &str) -> Result<u16, ParseError> {
s.parse::<u16>()
.map_err(|e| ParseError::InvalidPort(e))
}
fn get_url(config: &Config) -> Result<String, Error> {
config.url()
.map(|u| format!("https://{}", u))
}
```
### and_then (flatMap)
```rust
fn validate_and_save(input: &str) -> Result<(), Error> {
validate(input)
.and_then(|valid| save(valid))
.and_then(|saved| notify(saved))
}
```
### unwrap_or and unwrap_or_else
```rust
// Default value
let port = config.port().unwrap_or(8080);
// Computed default
let port = config.port().unwrap_or_else(|| find_free_port());
// Default for Result
let data = load_data().unwrap_or_default();
```
---
## Pattern: Early Return vs Combinators
### Early Return Style
```rust
fn process(input: &str) -> Result<Output, Error> {
let step1 = validate(input)?;
if !step1.is_valid {
return Err(Error::Invalid);
}
let step2 = transform(step1)?;
let step3 = save(step2)?;
Ok(step3)
}
```
### Combinator Style
```rust
fn process(input: &str) -> Result<Output, Error> {
validate(input)
.and_then(|s| {
if s.is_valid {
Ok(s)
} else {
Err(Error::Invalid)
}
})
.and_then(transform)
.and_then(save)
}
```
### When to Use Which
| Style | Best For |
|-------|----------|
| Early return (`?`) | Most cases, clearer flow |
| Combinators | Functional pipelines, one-liners |
| Match | Complex branching on errors |
---
## Panic vs Result
### When to Panic
```rust
// 1. Unrecoverable programmer error
fn get_config() -> &'static Config {
CONFIG.get().expect("config must be initialized")
}
// 2. In tests
#[test]
fn test_parsing() {
let result = parse("valid").unwrap(); // OK in tests
assert_eq!(result, expected);
}
// 3. Prototype/examples
fn main() {
let data = load().unwrap(); // OK for quick examples
}
```
### When to Return Result
```rust
// 1. Any I/O operation
fn read_file(path: &str) -> Result<String, io::Error>
// 2. User input validation
fn parse_port(s: &str) -> Result<u16, ParseError>
// 3. Network operations
async fn fetch(url: &str) -> Result<Response, Error>
// 4. Anything that can fail at runtime
fn connect(addr: &str) -> Result<Connection, Error>
```
---
## Error Context Best Practices
### Add Context at Boundaries
```rust
fn load_user_config(user_id: u64) -> Result<Config, Error> {
let path = format!("/home/{}/config.toml", user_id);
std::fs::read_to_string(&path)
.context(format!("failed to read config for user {}", user_id))?
// NOT: .context("failed to read file") // too generic
// ...
}
```
### Include Relevant Data
```rust
// Good: includes the problematic value
fn parse_age(s: &str) -> Result<u8, Error> {
s.parse()
.context(format!("invalid age value: '{}'", s))
}
// Bad: no context about what failed
fn parse_age(s: &str) -> Result<u8, Error> {
s.parse()
.context("parse error")
}
```

View File

@@ -1,221 +0,0 @@
---
name: m07-concurrency
description: "CRITICAL: Use for concurrency/async. Triggers: E0277 Send Sync, cannot be sent between threads, thread, spawn, channel, mpsc, Mutex, RwLock, Atomic, async, await, Future, tokio, deadlock, race condition, 并发, 线程, 异步, 死锁"
---
# Concurrency
> **Layer 1: Language Mechanics**
## Core Question
**Is this CPU-bound or I/O-bound, and what's the sharing model?**
Before choosing concurrency primitives:
- What's the workload type?
- What data needs to be shared?
- What's the thread safety requirement?
---
## Error → Design Question
| Error | Don't Just Say | Ask Instead |
|-------|----------------|-------------|
| E0277 Send | "Add Send bound" | Should this type cross threads? |
| E0277 Sync | "Wrap in Mutex" | Is shared access really needed? |
| Future not Send | "Use spawn_local" | Is async the right choice? |
| Deadlock | "Reorder locks" | Is the locking design correct? |
---
## Thinking Prompt
Before adding concurrency:
1. **What's the workload?**
- CPU-bound → threads (std::thread, rayon)
- I/O-bound → async (tokio, async-std)
- Mixed → hybrid approach
2. **What's the sharing model?**
- No sharing → message passing (channels)
- Immutable sharing → Arc<T>
- Mutable sharing → Arc<Mutex<T>> or Arc<RwLock<T>>
3. **What are the Send/Sync requirements?**
- Cross-thread ownership → Send
- Cross-thread references → Sync
- Single-thread async → spawn_local
---
## Trace Up ↑ (MANDATORY)
**CRITICAL**: Don't just fix the error. Trace UP to find domain constraints.
### Domain Detection Table
| Context Keywords | Load Domain Skill | Key Constraint |
|-----------------|-------------------|----------------|
| Web API, HTTP, axum, actix, handler | **domain-web** | Handlers run on any thread |
| 交易, 支付, trading, payment | **domain-fintech** | Audit + thread safety |
| gRPC, kubernetes, microservice | **domain-cloud-native** | Distributed tracing |
| CLI, terminal, clap | **domain-cli** | Usually single-thread OK |
### Example: Web API + Rc Error
```
"Rc cannot be sent between threads" in Web API context
↑ DETECT: "Web API" → Load domain-web
↑ FIND: domain-web says "Shared state must be thread-safe"
↑ FIND: domain-web says "Rc in state" is Common Mistake
↓ DESIGN: Use Arc<T> with State extractor
↓ IMPL: axum::extract::State<Arc<AppConfig>>
```
### Generic Trace
```
"Send not satisfied for my type"
↑ Ask: What domain is this? Load domain-* skill
↑ Ask: Does this type need to cross thread boundaries?
↑ Check: m09-domain (is the data model correct?)
```
| Situation | Trace To | Question |
|-----------|----------|----------|
| Send/Sync in Web | **domain-web** | What's the state management pattern? |
| Send/Sync in CLI | **domain-cli** | Is multi-thread really needed? |
| Mutex vs channels | m09-domain | Shared state or message passing? |
| Async vs threads | m10-performance | What's the workload profile? |
---
## Trace Down ↓
From design to implementation:
```
"Need parallelism for CPU work"
↓ Use: std::thread or rayon
"Need concurrency for I/O"
↓ Use: async/await with tokio
"Need to share immutable data across threads"
↓ Use: Arc<T>
"Need to share mutable data across threads"
↓ Use: Arc<Mutex<T>> or Arc<RwLock<T>>
↓ Or: channels for message passing
"Need simple atomic operations"
↓ Use: AtomicBool, AtomicUsize, etc.
```
---
## Send/Sync Markers
| Marker | Meaning | Example |
|--------|---------|---------|
| `Send` | Can transfer ownership between threads | Most types |
| `Sync` | Can share references between threads | `Arc<T>` |
| `!Send` | Must stay on one thread | `Rc<T>` |
| `!Sync` | No shared refs across threads | `RefCell<T>` |
## Quick Reference
| Pattern | Thread-Safe | Blocking | Use When |
|---------|-------------|----------|----------|
| `std::thread` | Yes | Yes | CPU-bound parallelism |
| `async/await` | Yes | No | I/O-bound concurrency |
| `Mutex<T>` | Yes | Yes | Shared mutable state |
| `RwLock<T>` | Yes | Yes | Read-heavy shared state |
| `mpsc::channel` | Yes | Optional | Message passing |
| `Arc<Mutex<T>>` | Yes | Yes | Shared mutable across threads |
## Decision Flowchart
```
What type of work?
├─ CPU-bound → std::thread or rayon
├─ I/O-bound → async/await
└─ Mixed → hybrid (spawn_blocking)
Need to share data?
├─ No → message passing (channels)
├─ Immutable → Arc<T>
└─ Mutable →
├─ Read-heavy → Arc<RwLock<T>>
└─ Write-heavy → Arc<Mutex<T>>
└─ Simple counter → AtomicUsize
Async context?
├─ Type is Send → tokio::spawn
├─ Type is !Send → spawn_local
└─ Blocking code → spawn_blocking
```
---
## Common Errors
| Error | Cause | Fix |
|-------|-------|-----|
| E0277 `Send` not satisfied | Non-Send in async | Use Arc or spawn_local |
| E0277 `Sync` not satisfied | Non-Sync shared | Wrap with Mutex |
| Deadlock | Lock ordering | Consistent lock order |
| `future is not Send` | Non-Send across await | Drop before await |
| `MutexGuard` across await | Guard held during suspend | Scope guard properly |
---
## Anti-Patterns
| Anti-Pattern | Why Bad | Better |
|--------------|---------|--------|
| Arc<Mutex<T>> everywhere | Contention, complexity | Message passing |
| thread::sleep in async | Blocks executor | tokio::time::sleep |
| Holding locks across await | Blocks other tasks | Scope locks tightly |
| Ignoring deadlock risk | Hard to debug | Lock ordering, try_lock |
---
## Async-Specific Patterns
### Avoid MutexGuard Across Await
```rust
// Bad: guard held across await
let guard = mutex.lock().await;
do_async().await; // guard still held!
// Good: scope the lock
{
let guard = mutex.lock().await;
// use guard
} // guard dropped
do_async().await;
```
### Non-Send Types in Async
```rust
// Rc is !Send, can't cross await in spawned task
// Option 1: use Arc instead
// Option 2: use spawn_local (single-thread runtime)
// Option 3: ensure Rc is dropped before .await
```
---
## Related Skills
| When | See |
|------|-----|
| Smart pointer choice | m02-resource |
| Interior mutability | m03-mutability |
| Performance tuning | m10-performance |
| Domain concurrency needs | domain-* |

View File

@@ -1,312 +0,0 @@
# Concurrency: Comparison with Other Languages
## Rust vs Go
### Concurrency Model
| Aspect | Rust | Go |
|--------|------|-----|
| Model | Ownership + Send/Sync | CSP (Communicating Sequential Processes) |
| Primitives | Arc, Mutex, channels | goroutines, channels |
| Safety | Compile-time | Runtime (race detector) |
| Async | async/await + runtime | Built-in scheduler |
### Goroutines vs Rust Tasks
```rust
// Rust: explicit about thread safety
use std::sync::Arc;
use tokio::sync::Mutex;
let data = Arc::new(Mutex::new(vec![]));
let data_clone = Arc::clone(&data);
tokio::spawn(async move {
let mut guard = data_clone.lock().await;
guard.push(1); // Safe: Mutex protects access
});
// Go: implicit sharing (potential race)
// data := []int{}
// go func() {
// data = append(data, 1) // RACE CONDITION!
// }()
```
### Channel Comparison
```rust
// Rust: typed channels with ownership
use tokio::sync::mpsc;
let (tx, mut rx) = mpsc::channel::<String>(100);
tokio::spawn(async move {
tx.send("hello".to_string()).await.unwrap();
// tx is moved, can't be used elsewhere
});
// Go: channels are more flexible but less safe
// ch := make(chan string, 100)
// go func() {
// ch <- "hello"
// // ch can still be used anywhere
// }()
```
---
## Rust vs Java
### Thread Safety Model
| Aspect | Rust | Java |
|--------|------|------|
| Safety | Compile-time (Send/Sync) | Runtime (synchronized, volatile) |
| Null | No null (Option) | NullPointerException risk |
| Locks | RAII (drop releases) | try-finally or try-with-resources |
| Memory | No GC | GC with stop-the-world |
### Synchronization Comparison
```rust
// Rust: lock is tied to data
use std::sync::Mutex;
let data = Mutex::new(vec![1, 2, 3]);
{
let mut guard = data.lock().unwrap();
guard.push(4);
} // lock released automatically
// Java: lock and data are separate
// List<Integer> data = new ArrayList<>();
// synchronized(data) {
// data.add(4);
// } // easy to forget synchronization elsewhere
```
### Thread Pool Comparison
```rust
// Rust: rayon for data parallelism
use rayon::prelude::*;
let sum: i32 = (0..1000)
.into_par_iter()
.map(|x| x * x)
.sum();
// Java: Stream API
// int sum = IntStream.range(0, 1000)
// .parallel()
// .map(x -> x * x)
// .sum();
```
---
## Rust vs C++
### Safety Guarantees
| Aspect | Rust | C++ |
|--------|------|-----|
| Data races | Prevented at compile-time | Undefined behavior |
| Deadlocks | Not prevented (same as C++) | Not prevented |
| Thread safety | Send/Sync traits | Convention only |
| Memory ordering | Explicit Ordering enum | memory_order enum |
### Atomic Comparison
```rust
// Rust: clear memory ordering
use std::sync::atomic::{AtomicI32, Ordering};
let counter = AtomicI32::new(0);
counter.fetch_add(1, Ordering::SeqCst);
let value = counter.load(Ordering::Acquire);
// C++: similar but without safety
// std::atomic<int> counter{0};
// counter.fetch_add(1, std::memory_order_seq_cst);
// int value = counter.load(std::memory_order_acquire);
```
### Mutex Comparison
```rust
// Rust: data protected by Mutex
use std::sync::Mutex;
struct SafeCounter {
count: Mutex<i32>, // Mutex contains the data
}
impl SafeCounter {
fn increment(&self) {
*self.count.lock().unwrap() += 1;
}
}
// C++: mutex separate from data (error-prone)
// class Counter {
// std::mutex mtx;
// int count; // NOT protected by type system
// public:
// void increment() {
// std::lock_guard<std::mutex> lock(mtx);
// count++;
// }
// void unsafe_increment() {
// count++; // Compiles! But wrong.
// }
// };
```
---
## Async Models Comparison
| Language | Model | Runtime |
|----------|-------|---------|
| Rust | async/await, zero-cost | tokio, async-std (bring your own) |
| Go | goroutines | Built-in scheduler |
| JavaScript | async/await, Promises | Event loop (single-threaded) |
| Python | async/await | asyncio (single-threaded) |
| Java | CompletableFuture, Virtual Threads | ForkJoinPool, Loom |
### Rust vs JavaScript Async
```rust
// Rust: async requires explicit runtime, can use multiple threads
#[tokio::main]
async fn main() {
let results = tokio::join!(
fetch("url1"), // runs concurrently
fetch("url2"),
);
}
// JavaScript: single-threaded event loop
// async function main() {
// const results = await Promise.all([
// fetch("url1"),
// fetch("url2"),
// ]);
// }
```
### Rust vs Python Async
```rust
// Rust: true parallelism possible
#[tokio::main(flavor = "multi_thread")]
async fn main() {
let handles: Vec<_> = urls
.into_iter()
.map(|url| tokio::spawn(fetch(url))) // spawns on thread pool
.collect();
for handle in handles {
let _ = handle.await;
}
}
// Python: asyncio is single-threaded (use ProcessPoolExecutor for CPU)
# async def main():
# tasks = [asyncio.create_task(fetch(url)) for url in urls]
# await asyncio.gather(*tasks) # all on same thread
```
---
## Send and Sync: Rust's Unique Feature
No other mainstream language has compile-time thread safety markers:
| Trait | Meaning | Auto-impl |
|-------|---------|-----------|
| `Send` | Safe to transfer between threads | Most types |
| `Sync` | Safe to share `&T` between threads | Types with thread-safe `&` |
| `!Send` | Must stay on one thread | Rc, raw pointers |
| `!Sync` | References can't be shared | RefCell, Cell |
### Why This Matters
```rust
// Rust PREVENTS this at compile time:
use std::rc::Rc;
let rc = Rc::new(42);
std::thread::spawn(move || {
println!("{}", rc); // ERROR: Rc is not Send
});
// In other languages, this would be a runtime bug:
// - Go: race detector might catch it
// - Java: undefined behavior
// - Python: GIL usually saves you
// - C++: undefined behavior
```
---
## Performance Characteristics
| Aspect | Rust | Go | Java | C++ |
|--------|------|-----|------|-----|
| Thread overhead | System threads or M:N | M:N (goroutines) | System or virtual | System threads |
| Context switch | OS-level or cooperative | Cheap (goroutines) | OS-level | OS-level |
| Memory | Predictable (no GC) | GC pauses | GC pauses | Predictable |
| Async overhead | Zero-cost futures | Runtime overhead | Boxing overhead | Depends |
### When to Use What
| Scenario | Best Choice |
|----------|-------------|
| CPU-bound parallelism | Rust (rayon), C++ |
| I/O-bound concurrency | Rust (tokio), Go, Node.js |
| Low latency required | Rust, C++ |
| Rapid development | Go, Python |
| Complex concurrent state | Rust (compile-time safety) |
---
## Mental Model Shifts
### From Go
```
Before: "Just use goroutines and channels"
After: "Explicitly declare what can be shared and how"
```
Key shifts:
- `Arc<Mutex<T>>` instead of implicit sharing
- Compiler enforces thread safety
- Async needs explicit runtime
### From Java
```
Before: "synchronized everywhere, hope for the best"
After: "Types encode thread safety, compiler enforces"
```
Key shifts:
- No need for synchronized keyword
- Mutex contains data, not separate
- No GC pauses in critical sections
### From C++
```
Before: "Be careful, read the docs, use sanitizers"
After: "Compiler catches data races, trust the type system"
```
Key shifts:
- Send/Sync replace convention
- RAII locks are mandatory, not optional
- Much harder to write incorrect concurrent code

View File

@@ -1,396 +0,0 @@
# Thread-Based Concurrency Patterns
## Thread Spawning Best Practices
### Basic Thread Spawn
```rust
use std::thread;
fn main() {
let handle = thread::spawn(|| {
println!("Hello from thread!");
42 // return value
});
let result = handle.join().unwrap();
println!("Thread returned: {}", result);
}
```
### Named Threads for Debugging
```rust
use std::thread;
let builder = thread::Builder::new()
.name("worker-1".to_string())
.stack_size(32 * 1024); // 32KB stack
let handle = builder.spawn(|| {
println!("Thread name: {:?}", thread::current().name());
}).unwrap();
```
### Scoped Threads (No 'static Required)
```rust
use std::thread;
fn process_data(data: &[u32]) -> Vec<u32> {
thread::scope(|s| {
let handles: Vec<_> = data
.chunks(2)
.map(|chunk| {
s.spawn(|| {
chunk.iter().map(|x| x * 2).collect::<Vec<_>>()
})
})
.collect();
handles
.into_iter()
.flat_map(|h| h.join().unwrap())
.collect()
})
}
fn main() {
let data = vec![1, 2, 3, 4, 5, 6];
let result = process_data(&data); // No 'static needed!
println!("{:?}", result);
}
```
---
## Shared State Patterns
### Arc + Mutex (Read-Write)
```rust
use std::sync::{Arc, Mutex};
use std::thread;
fn shared_counter() {
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
let counter = Arc::clone(&counter);
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Result: {}", *counter.lock().unwrap());
}
```
### Arc + RwLock (Read-Heavy)
```rust
use std::sync::{Arc, RwLock};
use std::thread;
fn read_heavy_cache() {
let cache = Arc::new(RwLock::new(vec![1, 2, 3]));
// Many readers
for i in 0..5 {
let cache = Arc::clone(&cache);
thread::spawn(move || {
let data = cache.read().unwrap();
println!("Reader {}: {:?}", i, *data);
});
}
// Occasional writer
{
let cache = Arc::clone(&cache);
thread::spawn(move || {
let mut data = cache.write().unwrap();
data.push(4);
println!("Writer: added element");
});
}
}
```
### Atomic for Simple Types
```rust
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::thread;
fn atomic_counter() {
let counter = Arc::new(AtomicUsize::new(0));
let mut handles = vec![];
for _ in 0..10 {
let counter = Arc::clone(&counter);
handles.push(thread::spawn(move || {
for _ in 0..1000 {
counter.fetch_add(1, Ordering::SeqCst);
}
}));
}
for handle in handles {
handle.join().unwrap();
}
println!("Result: {}", counter.load(Ordering::SeqCst));
}
```
---
## Channel Patterns
### MPSC Channel
```rust
use std::sync::mpsc;
use std::thread;
fn producer_consumer() {
let (tx, rx) = mpsc::channel();
// Multiple producers
for i in 0..3 {
let tx = tx.clone();
thread::spawn(move || {
for j in 0..5 {
tx.send(format!("msg {}-{}", i, j)).unwrap();
}
});
}
drop(tx); // Drop original sender
// Single consumer
for received in rx {
println!("Got: {}", received);
}
}
```
### Sync Channel (Bounded)
```rust
use std::sync::mpsc;
use std::thread;
fn bounded_channel() {
let (tx, rx) = mpsc::sync_channel(2); // buffer size 2
thread::spawn(move || {
for i in 0..5 {
println!("Sending {}", i);
tx.send(i).unwrap(); // blocks if buffer full
println!("Sent {}", i);
}
});
thread::sleep(std::time::Duration::from_millis(500));
for received in rx {
println!("Received: {}", received);
thread::sleep(std::time::Duration::from_millis(100));
}
}
```
---
## Thread Pool Patterns
### Using rayon for Parallel Iteration
```rust
use rayon::prelude::*;
fn parallel_map() {
let numbers: Vec<i32> = (0..1000).collect();
let squares: Vec<i32> = numbers
.par_iter() // parallel iterator
.map(|x| x * x)
.collect();
println!("Processed {} items", squares.len());
}
fn parallel_filter_map() {
let data: Vec<String> = get_data();
let results: Vec<_> = data
.par_iter()
.filter(|s| !s.is_empty())
.map(|s| expensive_process(s))
.collect();
}
```
### Custom Thread Pool with crossbeam
```rust
use crossbeam::channel;
use std::thread;
fn custom_pool(num_workers: usize) {
let (tx, rx) = channel::bounded::<Box<dyn FnOnce() + Send>>(100);
// Spawn workers
let workers: Vec<_> = (0..num_workers)
.map(|_| {
let rx = rx.clone();
thread::spawn(move || {
while let Ok(task) = rx.recv() {
task();
}
})
})
.collect();
// Submit tasks
for i in 0..100 {
tx.send(Box::new(move || {
println!("Processing task {}", i);
})).unwrap();
}
drop(tx); // Close channel
for worker in workers {
worker.join().unwrap();
}
}
```
---
## Synchronization Primitives
### Barrier (Wait for All)
```rust
use std::sync::{Arc, Barrier};
use std::thread;
fn barrier_example() {
let barrier = Arc::new(Barrier::new(3));
let mut handles = vec![];
for i in 0..3 {
let barrier = Arc::clone(&barrier);
handles.push(thread::spawn(move || {
println!("Thread {} starting", i);
thread::sleep(std::time::Duration::from_millis(i as u64 * 100));
barrier.wait(); // All threads wait here
println!("Thread {} after barrier", i);
}));
}
for handle in handles {
handle.join().unwrap();
}
}
```
### Condvar (Condition Variable)
```rust
use std::sync::{Arc, Condvar, Mutex};
use std::thread;
fn condvar_example() {
let pair = Arc::new((Mutex::new(false), Condvar::new()));
let pair_clone = Arc::clone(&pair);
// Waiter thread
let waiter = thread::spawn(move || {
let (lock, cvar) = &*pair_clone;
let mut started = lock.lock().unwrap();
while !*started {
started = cvar.wait(started).unwrap();
}
println!("Waiter: condition met!");
});
// Notifier
thread::sleep(std::time::Duration::from_millis(100));
let (lock, cvar) = &*pair;
{
let mut started = lock.lock().unwrap();
*started = true;
}
cvar.notify_one();
waiter.join().unwrap();
}
```
### Once (One-Time Initialization)
```rust
use std::sync::Once;
static INIT: Once = Once::new();
static mut CONFIG: Option<Config> = None;
fn get_config() -> &'static Config {
INIT.call_once(|| {
unsafe {
CONFIG = Some(load_config());
}
});
unsafe { CONFIG.as_ref().unwrap() }
}
// Better: use once_cell or lazy_static
use once_cell::sync::Lazy;
static CONFIG: Lazy<Config> = Lazy::new(|| {
load_config()
});
```
---
## Error Handling in Threads
### Handling Panics
```rust
use std::thread;
fn handle_panic() {
let handle = thread::spawn(|| {
panic!("Thread panicked!");
});
match handle.join() {
Ok(_) => println!("Thread completed successfully"),
Err(e) => {
if let Some(s) = e.downcast_ref::<&str>() {
println!("Thread panicked with: {}", s);
} else if let Some(s) = e.downcast_ref::<String>() {
println!("Thread panicked with: {}", s);
} else {
println!("Thread panicked with unknown error");
}
}
}
}
```
### Catching Panics
```rust
use std::panic;
fn catch_panic() {
let result = panic::catch_unwind(|| {
risky_operation()
});
match result {
Ok(value) => println!("Success: {:?}", value),
Err(_) => println!("Operation panicked, continuing..."),
}
}
```

View File

@@ -1,409 +0,0 @@
# Async Patterns in Rust
## Task Spawning
### Basic Spawn
```rust
use tokio::task;
#[tokio::main]
async fn main() {
// Spawn a task that runs concurrently
let handle = task::spawn(async {
expensive_computation().await
});
// Do other work while task runs
other_work().await;
// Wait for result
let result = handle.await.unwrap();
}
```
### Spawn with Shared State
```rust
use std::sync::Arc;
use tokio::sync::Mutex;
async fn process_with_state() {
let state = Arc::new(Mutex::new(vec![]));
let handles: Vec<_> = (0..10)
.map(|i| {
let state = Arc::clone(&state);
tokio::spawn(async move {
let mut guard = state.lock().await;
guard.push(i);
})
})
.collect();
// Wait for all tasks
for handle in handles {
handle.await.unwrap();
}
}
```
---
## Select Pattern
### Racing Multiple Futures
```rust
use tokio::select;
use tokio::time::{sleep, Duration};
async fn first_response() {
select! {
result = fetch_from_server_a() => {
println!("A responded first: {:?}", result);
}
result = fetch_from_server_b() => {
println!("B responded first: {:?}", result);
}
}
}
```
### Select with Timeout
```rust
use tokio::time::timeout;
async fn with_timeout() -> Result<Data, Error> {
select! {
result = fetch_data() => result,
_ = sleep(Duration::from_secs(5)) => {
Err(Error::Timeout)
}
}
}
// Or use timeout directly
async fn with_timeout2() -> Result<Data, Error> {
timeout(Duration::from_secs(5), fetch_data())
.await
.map_err(|_| Error::Timeout)?
}
```
### Select with Channel
```rust
use tokio::sync::mpsc;
async fn process_messages(mut rx: mpsc::Receiver<Message>) {
loop {
select! {
Some(msg) = rx.recv() => {
handle_message(msg).await;
}
_ = tokio::signal::ctrl_c() => {
println!("Shutting down...");
break;
}
}
}
}
```
---
## Channel Patterns
### MPSC (Multi-Producer, Single-Consumer)
```rust
use tokio::sync::mpsc;
async fn producer_consumer() {
let (tx, mut rx) = mpsc::channel(100);
// Spawn producers
for i in 0..3 {
let tx = tx.clone();
tokio::spawn(async move {
tx.send(format!("Message from {}", i)).await.unwrap();
});
}
// Drop original sender so channel closes
drop(tx);
// Consume
while let Some(msg) = rx.recv().await {
println!("Received: {}", msg);
}
}
```
### Oneshot (Single-Shot Response)
```rust
use tokio::sync::oneshot;
async fn request_response() {
let (tx, rx) = oneshot::channel();
tokio::spawn(async move {
let result = compute_something().await;
tx.send(result).unwrap();
});
// Wait for response
let response = rx.await.unwrap();
}
```
### Broadcast (Multi-Consumer)
```rust
use tokio::sync::broadcast;
async fn pub_sub() {
let (tx, _) = broadcast::channel(16);
// Subscribe multiple consumers
let mut rx1 = tx.subscribe();
let mut rx2 = tx.subscribe();
tokio::spawn(async move {
while let Ok(msg) = rx1.recv().await {
println!("Consumer 1: {}", msg);
}
});
tokio::spawn(async move {
while let Ok(msg) = rx2.recv().await {
println!("Consumer 2: {}", msg);
}
});
// Publish
tx.send("Hello").unwrap();
}
```
### Watch (Single Latest Value)
```rust
use tokio::sync::watch;
async fn config_updates() {
let (tx, mut rx) = watch::channel(Config::default());
// Consumer watches for changes
tokio::spawn(async move {
while rx.changed().await.is_ok() {
let config = rx.borrow();
apply_config(&config);
}
});
// Update config
tx.send(Config::new()).unwrap();
}
```
---
## Structured Concurrency
### JoinSet for Task Groups
```rust
use tokio::task::JoinSet;
async fn parallel_fetch(urls: Vec<String>) -> Vec<Result<Response, Error>> {
let mut set = JoinSet::new();
for url in urls {
set.spawn(async move {
fetch(&url).await
});
}
let mut results = vec![];
while let Some(res) = set.join_next().await {
results.push(res.unwrap());
}
results
}
```
### Scoped Tasks (no 'static)
```rust
// Using tokio-scoped or async-scoped crate
use async_scoped::TokioScope;
async fn scoped_example(data: &[u32]) {
let results = TokioScope::scope_and_block(|scope| {
for item in data {
scope.spawn(async move {
process(item).await
});
}
});
}
```
---
## Cancellation Patterns
### Using CancellationToken
```rust
use tokio_util::sync::CancellationToken;
async fn cancellable_task(token: CancellationToken) {
loop {
select! {
_ = token.cancelled() => {
println!("Task cancelled");
break;
}
_ = do_work() => {
// Continue working
}
}
}
}
async fn main_with_cancellation() {
let token = CancellationToken::new();
let task_token = token.clone();
let handle = tokio::spawn(cancellable_task(task_token));
// Cancel after some condition
tokio::time::sleep(Duration::from_secs(5)).await;
token.cancel();
handle.await.unwrap();
}
```
### Graceful Shutdown
```rust
async fn serve_with_shutdown(shutdown: impl Future) {
let server = TcpListener::bind("0.0.0.0:8080").await.unwrap();
loop {
select! {
Ok((socket, _)) = server.accept() => {
tokio::spawn(handle_connection(socket));
}
_ = &mut shutdown => {
println!("Shutting down...");
break;
}
}
}
}
#[tokio::main]
async fn main() {
let ctrl_c = async {
tokio::signal::ctrl_c().await.unwrap();
};
serve_with_shutdown(ctrl_c).await;
}
```
---
## Backpressure Patterns
### Bounded Channels
```rust
use tokio::sync::mpsc;
async fn with_backpressure() {
// Buffer of 10 - producers will wait if full
let (tx, mut rx) = mpsc::channel(10);
let producer = tokio::spawn(async move {
for i in 0..1000 {
// This will wait if channel is full
tx.send(i).await.unwrap();
}
});
let consumer = tokio::spawn(async move {
while let Some(item) = rx.recv().await {
// Slow consumer
tokio::time::sleep(Duration::from_millis(10)).await;
process(item);
}
});
let _ = tokio::join!(producer, consumer);
}
```
### Semaphore for Rate Limiting
```rust
use tokio::sync::Semaphore;
use std::sync::Arc;
async fn rate_limited_requests(urls: Vec<String>) {
let semaphore = Arc::new(Semaphore::new(10)); // max 10 concurrent
let handles: Vec<_> = urls
.into_iter()
.map(|url| {
let sem = Arc::clone(&semaphore);
tokio::spawn(async move {
let _permit = sem.acquire().await.unwrap();
fetch(&url).await
})
})
.collect();
for handle in handles {
handle.await.unwrap();
}
}
```
---
## Error Handling in Async
### Propagating Errors
```rust
async fn fetch_and_parse(url: &str) -> Result<Data, Error> {
let response = fetch(url).await?;
let data = parse(response).await?;
Ok(data)
}
```
### Handling Task Panics
```rust
async fn robust_spawn() {
let handle = tokio::spawn(async {
risky_operation().await
});
match handle.await {
Ok(result) => println!("Success: {:?}", result),
Err(e) if e.is_panic() => {
println!("Task panicked: {:?}", e);
}
Err(e) => {
println!("Task cancelled: {:?}", e);
}
}
}
```
### Try-Join for Multiple Results
```rust
use tokio::try_join;
async fn fetch_all() -> Result<(A, B, C), Error> {
// All must succeed, or first error returned
try_join!(
fetch_a(),
fetch_b(),
fetch_c(),
)
}
```

View File

@@ -1,331 +0,0 @@
# Common Concurrency Errors & Fixes
## E0277: Cannot Send Between Threads
### Error Pattern
```rust
use std::rc::Rc;
let data = Rc::new(42);
std::thread::spawn(move || {
println!("{}", data); // ERROR: Rc<i32> cannot be sent between threads
});
```
### Fix Options
**Option 1: Use Arc instead**
```rust
use std::sync::Arc;
let data = Arc::new(42);
let data_clone = Arc::clone(&data);
std::thread::spawn(move || {
println!("{}", data_clone); // OK: Arc is Send
});
```
**Option 2: Move owned data**
```rust
let data = 42; // i32 is Copy and Send
std::thread::spawn(move || {
println!("{}", data); // OK
});
```
---
## E0277: Cannot Share Between Threads (Not Sync)
### Error Pattern
```rust
use std::cell::RefCell;
use std::sync::Arc;
let data = Arc::new(RefCell::new(42));
// ERROR: RefCell is not Sync
```
### Fix Options
**Option 1: Use Mutex for thread-safe interior mutability**
```rust
use std::sync::{Arc, Mutex};
let data = Arc::new(Mutex::new(42));
let data_clone = Arc::clone(&data);
std::thread::spawn(move || {
let mut guard = data_clone.lock().unwrap();
*guard += 1;
});
```
**Option 2: Use RwLock for read-heavy workloads**
```rust
use std::sync::{Arc, RwLock};
let data = Arc::new(RwLock::new(42));
let data_clone = Arc::clone(&data);
std::thread::spawn(move || {
let guard = data_clone.read().unwrap();
println!("{}", *guard);
});
```
---
## Deadlock Patterns
### Pattern 1: Lock Ordering Deadlock
```rust
// DANGER: potential deadlock
use std::sync::{Arc, Mutex};
let a = Arc::new(Mutex::new(1));
let b = Arc::new(Mutex::new(2));
// Thread 1: locks a then b
let a1 = Arc::clone(&a);
let b1 = Arc::clone(&b);
std::thread::spawn(move || {
let _a = a1.lock().unwrap();
let _b = b1.lock().unwrap(); // waits for b
});
// Thread 2: locks b then a (opposite order!)
let a2 = Arc::clone(&a);
let b2 = Arc::clone(&b);
std::thread::spawn(move || {
let _b = b2.lock().unwrap();
let _a = a2.lock().unwrap(); // waits for a - DEADLOCK
});
```
### Fix: Consistent Lock Ordering
```rust
// SAFE: always lock in same order (a before b)
std::thread::spawn(move || {
let _a = a1.lock().unwrap();
let _b = b1.lock().unwrap();
});
std::thread::spawn(move || {
let _a = a2.lock().unwrap(); // same order
let _b = b2.lock().unwrap();
});
```
### Pattern 2: Self-Deadlock
```rust
// DANGER: locking same mutex twice
let m = Mutex::new(42);
let _g1 = m.lock().unwrap();
let _g2 = m.lock().unwrap(); // DEADLOCK on std::Mutex
// FIX: use parking_lot::ReentrantMutex if needed
// or restructure code to avoid double locking
```
---
## Mutex Guard Across Await
### Error Pattern
```rust
use std::sync::Mutex;
use tokio::time::sleep;
async fn bad_async() {
let m = Mutex::new(42);
let guard = m.lock().unwrap();
sleep(Duration::from_secs(1)).await; // WARNING: guard held across await
println!("{}", *guard);
}
```
### Fix Options
**Option 1: Scope the lock**
```rust
async fn good_async() {
let m = Mutex::new(42);
let value = {
let guard = m.lock().unwrap();
*guard // copy value
}; // guard dropped here
sleep(Duration::from_secs(1)).await;
println!("{}", value);
}
```
**Option 2: Use tokio::sync::Mutex**
```rust
use tokio::sync::Mutex;
async fn good_async() {
let m = Mutex::new(42);
let guard = m.lock().await; // async lock
sleep(Duration::from_secs(1)).await; // OK with tokio::Mutex
println!("{}", *guard);
}
```
---
## Data Race Prevention
### Pattern: Missing Synchronization
```rust
// This WON'T compile - Rust prevents data races
use std::sync::Arc;
let data = Arc::new(0);
let d1 = Arc::clone(&data);
let d2 = Arc::clone(&data);
std::thread::spawn(move || {
// *d1 += 1; // ERROR: cannot mutate through Arc
});
std::thread::spawn(move || {
// *d2 += 1; // ERROR: cannot mutate through Arc
});
```
### Fix: Add Synchronization
```rust
use std::sync::{Arc, Mutex};
use std::sync::atomic::{AtomicI32, Ordering};
// Option 1: Mutex
let data = Arc::new(Mutex::new(0));
let d1 = Arc::clone(&data);
std::thread::spawn(move || {
*d1.lock().unwrap() += 1;
});
// Option 2: Atomic (for simple types)
let data = Arc::new(AtomicI32::new(0));
let d1 = Arc::clone(&data);
std::thread::spawn(move || {
d1.fetch_add(1, Ordering::SeqCst);
});
```
---
## Channel Errors
### Disconnected Channel
```rust
use std::sync::mpsc;
let (tx, rx) = mpsc::channel();
drop(tx); // sender dropped
match rx.recv() {
Ok(v) => println!("{}", v),
Err(_) => println!("channel disconnected"), // this happens
}
```
### Fix: Handle Disconnection
```rust
// Use try_recv for non-blocking
loop {
match rx.try_recv() {
Ok(msg) => handle(msg),
Err(TryRecvError::Empty) => continue,
Err(TryRecvError::Disconnected) => break,
}
}
// Or iterate (stops on disconnect)
for msg in rx {
handle(msg);
}
```
---
## Async Common Errors
### Forgetting to Spawn
```rust
// WRONG: future not polled
async fn fetch_data() -> Result<Data, Error> { ... }
fn process() {
fetch_data(); // does nothing! returns Future that's dropped
}
// RIGHT: await or spawn
async fn process() {
let data = fetch_data().await; // awaited
}
fn process_sync() {
tokio::spawn(fetch_data()); // spawned
}
```
### Blocking in Async Context
```rust
// WRONG: blocks the executor
async fn bad() {
std::thread::sleep(Duration::from_secs(1)); // blocks!
std::fs::read_to_string("file.txt").unwrap(); // blocks!
}
// RIGHT: use async versions
async fn good() {
tokio::time::sleep(Duration::from_secs(1)).await;
tokio::fs::read_to_string("file.txt").await.unwrap();
}
// Or spawn_blocking for CPU-bound work
async fn compute() {
let result = tokio::task::spawn_blocking(|| {
heavy_computation() // OK to block here
}).await.unwrap();
}
```
---
## Thread Panic Handling
### Unhandled Panic
```rust
let handle = std::thread::spawn(|| {
panic!("oops");
});
// Main thread continues, might miss the error
handle.join().unwrap(); // panics here
```
### Proper Error Handling
```rust
let handle = std::thread::spawn(|| {
panic!("oops");
});
match handle.join() {
Ok(result) => println!("Success: {:?}", result),
Err(e) => println!("Thread panicked: {:?}", e),
}
// For async: use catch_unwind
use std::panic;
async fn safe_task() {
let result = panic::catch_unwind(|| {
risky_operation()
});
match result {
Ok(v) => use_value(v),
Err(_) => log_error("task panicked"),
}
}
```

View File

@@ -1,173 +0,0 @@
---
name: m09-domain
description: "CRITICAL: Use for domain modeling. Triggers: domain model, DDD, domain-driven design, entity, value object, aggregate, repository pattern, business rules, validation, invariant, 领域模型, 领域驱动设计, 业务规则"
---
# Domain Modeling
> **Layer 2: Design Choices**
## Core Question
**What is this concept's role in the domain?**
Before modeling in code, understand:
- Is it an Entity (identity matters) or Value Object (interchangeable)?
- What invariants must be maintained?
- Where are the aggregate boundaries?
---
## Domain Concept → Rust Pattern
| Domain Concept | Rust Pattern | Ownership Implication |
|----------------|--------------|----------------------|
| Entity | struct + Id | Owned, unique identity |
| Value Object | struct + Clone/Copy | Shareable, immutable |
| Aggregate Root | struct owns children | Clear ownership tree |
| Repository | trait | Abstracts persistence |
| Domain Event | enum | Captures state changes |
| Service | impl block / free fn | Stateless operations |
---
## Thinking Prompt
Before creating a domain type:
1. **What's the concept's identity?**
- Needs unique identity → Entity (Id field)
- Interchangeable by value → Value Object (Clone/Copy)
2. **What invariants must hold?**
- Always valid → private fields + validated constructor
- Transition rules → type state pattern
3. **Who owns this data?**
- Single owner (parent) → owned field
- Shared reference → Arc/Rc
- Weak reference → Weak
---
## Trace Up ↑
To domain constraints (Layer 3):
```
"How should I model a Transaction?"
↑ Ask: What domain rules govern transactions?
↑ Check: domain-fintech (audit, precision requirements)
↑ Check: Business stakeholders (what invariants?)
```
| Design Question | Trace To | Ask |
|-----------------|----------|-----|
| Entity vs Value Object | domain-* | What makes two instances "the same"? |
| Aggregate boundaries | domain-* | What must be consistent together? |
| Validation rules | domain-* | What business rules apply? |
---
## Trace Down ↓
To implementation (Layer 1):
```
"Model as Entity"
↓ m01-ownership: Owned, unique
↓ m05-type-driven: Newtype for Id
"Model as Value Object"
↓ m01-ownership: Clone/Copy OK
↓ m05-type-driven: Validate at construction
"Model as Aggregate"
↓ m01-ownership: Parent owns children
↓ m02-resource: Consider Rc for shared within aggregate
```
---
## Quick Reference
| DDD Concept | Rust Pattern | Example |
|-------------|--------------|---------|
| Value Object | Newtype | `struct Email(String);` |
| Entity | Struct + ID | `struct User { id: UserId, ... }` |
| Aggregate | Module boundary | `mod order { ... }` |
| Repository | Trait | `trait UserRepo { fn find(...) }` |
| Domain Event | Enum | `enum OrderEvent { Created, ... }` |
## Pattern Templates
### Value Object
```rust
struct Email(String);
impl Email {
pub fn new(s: &str) -> Result<Self, ValidationError> {
validate_email(s)?;
Ok(Self(s.to_string()))
}
}
```
### Entity
```rust
struct UserId(Uuid);
struct User {
id: UserId,
email: Email,
// ... other fields
}
impl PartialEq for User {
fn eq(&self, other: &Self) -> bool {
self.id == other.id // Identity equality
}
}
```
### Aggregate
```rust
mod order {
pub struct Order {
id: OrderId,
items: Vec<OrderItem>, // Owned children
// ...
}
impl Order {
pub fn add_item(&mut self, item: OrderItem) {
// Enforce aggregate invariants
}
}
}
```
---
## Common Mistakes
| Mistake | Why Wrong | Better |
|---------|-----------|--------|
| Primitive obsession | No type safety | Newtype wrappers |
| Public fields with invariants | Invariants violated | Private + accessor |
| Leaked aggregate internals | Broken encapsulation | Methods on root |
| String for semantic types | No validation | Validated newtype |
---
## Related Skills
| When | See |
|------|-----|
| Type-driven implementation | m05-type-driven |
| Ownership for aggregates | m01-ownership |
| Domain error handling | m13-domain-error |
| Specific domain rules | domain-* |

View File

@@ -1,156 +0,0 @@
---
name: m10-performance
description: "CRITICAL: Use for performance optimization. Triggers: performance, optimization, benchmark, profiling, flamegraph, criterion, slow, fast, allocation, cache, SIMD, make it faster, 性能优化, 基准测试"
---
# Performance Optimization
> **Layer 2: Design Choices**
## Core Question
**What's the bottleneck, and is optimization worth it?**
Before optimizing:
- Have you measured? (Don't guess)
- What's the acceptable performance?
- Will optimization add complexity?
---
## Performance Decision → Implementation
| Goal | Design Choice | Implementation |
|------|---------------|----------------|
| Reduce allocations | Pre-allocate, reuse | `with_capacity`, object pools |
| Improve cache | Contiguous data | `Vec`, `SmallVec` |
| Parallelize | Data parallelism | `rayon`, threads |
| Avoid copies | Zero-copy | References, `Cow<T>` |
| Reduce indirection | Inline data | `smallvec`, arrays |
---
## Thinking Prompt
Before optimizing:
1. **Have you measured?**
- Profile first → flamegraph, perf
- Benchmark → criterion, cargo bench
- Identify actual hotspots
2. **What's the priority?**
- Algorithm (10x-1000x improvement)
- Data structure (2x-10x)
- Allocation (2x-5x)
- Cache (1.5x-3x)
3. **What's the trade-off?**
- Complexity vs speed
- Memory vs CPU
- Latency vs throughput
---
## Trace Up ↑
To domain constraints (Layer 3):
```
"How fast does this need to be?"
↑ Ask: What's the performance SLA?
↑ Check: domain-* (latency requirements)
↑ Check: Business requirements (acceptable response time)
```
| Question | Trace To | Ask |
|----------|----------|-----|
| Latency requirements | domain-* | What's acceptable response time? |
| Throughput needs | domain-* | How many requests per second? |
| Memory constraints | domain-* | What's the memory budget? |
---
## Trace Down ↓
To implementation (Layer 1):
```
"Need to reduce allocations"
↓ m01-ownership: Use references, avoid clone
↓ m02-resource: Pre-allocate with_capacity
"Need to parallelize"
↓ m07-concurrency: Choose rayon or threads
↓ m07-concurrency: Consider async for I/O-bound
"Need cache efficiency"
↓ Data layout: Prefer Vec over HashMap when possible
↓ Access patterns: Sequential over random access
```
---
## Quick Reference
| Tool | Purpose |
|------|---------|
| `cargo bench` | Micro-benchmarks |
| `criterion` | Statistical benchmarks |
| `perf` / `flamegraph` | CPU profiling |
| `heaptrack` | Allocation tracking |
| `valgrind` / `cachegrind` | Cache analysis |
## Optimization Priority
```
1. Algorithm choice (10x - 1000x)
2. Data structure (2x - 10x)
3. Allocation reduction (2x - 5x)
4. Cache optimization (1.5x - 3x)
5. SIMD/Parallelism (2x - 8x)
```
## Common Techniques
| Technique | When | How |
|-----------|------|-----|
| Pre-allocation | Known size | `Vec::with_capacity(n)` |
| Avoid cloning | Hot paths | Use references or `Cow<T>` |
| Batch operations | Many small ops | Collect then process |
| SmallVec | Usually small | `smallvec::SmallVec<[T; N]>` |
| Inline buffers | Fixed-size data | Arrays over Vec |
---
## Common Mistakes
| Mistake | Why Wrong | Better |
|---------|-----------|--------|
| Optimize without profiling | Wrong target | Profile first |
| Benchmark in debug mode | Meaningless | Always `--release` |
| Use LinkedList | Cache unfriendly | `Vec` or `VecDeque` |
| Hidden `.clone()` | Unnecessary allocs | Use references |
| Premature optimization | Wasted effort | Make it work first |
---
## Anti-Patterns
| Anti-Pattern | Why Bad | Better |
|--------------|---------|--------|
| Clone to avoid lifetimes | Performance cost | Proper ownership |
| Box everything | Indirection cost | Stack when possible |
| HashMap for small sets | Overhead | Vec with linear search |
| String concat in loop | O(n^2) | `String::with_capacity` or `format!` |
---
## Related Skills
| When | See |
|------|-----|
| Reducing clones | m01-ownership |
| Concurrency options | m07-concurrency |
| Smart pointer choice | m02-resource |
| Domain requirements | domain-* |

View File

@@ -1,365 +0,0 @@
# Rust Performance Optimization Guide
## Profiling First
### Tools
```bash
# CPU profiling
cargo install flamegraph
cargo flamegraph --bin myapp
# Memory profiling
cargo install cargo-instruments # macOS
heaptrack ./target/release/myapp # Linux
# Benchmarking
cargo bench # with criterion
# Cache analysis
valgrind --tool=cachegrind ./target/release/myapp
```
### Criterion Benchmarks
```rust
use criterion::{criterion_group, criterion_main, Criterion};
fn benchmark_parse(c: &mut Criterion) {
let input = "test data".repeat(1000);
c.bench_function("parse_v1", |b| {
b.iter(|| parse_v1(&input))
});
c.bench_function("parse_v2", |b| {
b.iter(|| parse_v2(&input))
});
}
criterion_group!(benches, benchmark_parse);
criterion_main!(benches);
```
---
## Common Optimizations
### 1. Avoid Unnecessary Allocations
```rust
// BAD: allocates on every call
fn to_uppercase(s: &str) -> String {
s.to_uppercase()
}
// GOOD: return Cow, allocate only if needed
use std::borrow::Cow;
fn to_uppercase(s: &str) -> Cow<'_, str> {
if s.chars().all(|c| c.is_uppercase()) {
Cow::Borrowed(s)
} else {
Cow::Owned(s.to_uppercase())
}
}
```
### 2. Reuse Allocations
```rust
// BAD: creates new Vec each iteration
for item in items {
let mut buffer = Vec::new();
process(&mut buffer, item);
}
// GOOD: reuse buffer
let mut buffer = Vec::new();
for item in items {
buffer.clear();
process(&mut buffer, item);
}
```
### 3. Use Appropriate Collections
| Need | Collection | Notes |
|------|------------|-------|
| Sequential access | `Vec<T>` | Best cache locality |
| Random access by key | `HashMap<K, V>` | O(1) lookup |
| Ordered keys | `BTreeMap<K, V>` | O(log n) lookup |
| Small sets (<20) | `Vec<T>` + linear search | Lower overhead |
| FIFO queue | `VecDeque<T>` | O(1) push/pop both ends |
### 4. Pre-allocate Capacity
```rust
// BAD: many reallocations
let mut v = Vec::new();
for i in 0..10000 {
v.push(i);
}
// GOOD: single allocation
let mut v = Vec::with_capacity(10000);
for i in 0..10000 {
v.push(i);
}
```
---
## String Optimization
### Avoid String Concatenation in Loops
```rust
// BAD: O(n²) allocations
let mut result = String::new();
for s in strings {
result = result + &s;
}
// GOOD: O(n) with push_str
let mut result = String::new();
for s in strings {
result.push_str(&s);
}
// BETTER: pre-calculate capacity
let total_len: usize = strings.iter().map(|s| s.len()).sum();
let mut result = String::with_capacity(total_len);
for s in strings {
result.push_str(&s);
}
// BEST: use join for simple cases
let result = strings.join("");
```
### Use &str When Possible
```rust
// BAD: requires allocation
fn greet(name: String) {
println!("Hello, {}", name);
}
// GOOD: borrows, no allocation
fn greet(name: &str) {
println!("Hello, {}", name);
}
// Works with both:
greet("world"); // &str
greet(&String::from("world")); // &String coerces to &str
```
---
## Iterator Optimization
### Use Iterators Over Indexing
```rust
// BAD: bounds checking on each access
let mut sum = 0;
for i in 0..vec.len() {
sum += vec[i];
}
// GOOD: no bounds checking
let sum: i32 = vec.iter().sum();
// GOOD: when index needed
for (i, item) in vec.iter().enumerate() {
// ...
}
```
### Lazy Evaluation
```rust
// Iterators are lazy - computation happens at collect
let result: Vec<_> = data
.iter()
.filter(|x| x.is_valid())
.map(|x| x.process())
.take(10) // stop after 10 items
.collect();
```
### Avoid Collecting When Not Needed
```rust
// BAD: unnecessary intermediate allocation
let filtered: Vec<_> = items.iter().filter(|x| x.valid).collect();
let count = filtered.len();
// GOOD: no allocation
let count = items.iter().filter(|x| x.valid).count();
```
---
## Parallelism with Rayon
```rust
use rayon::prelude::*;
// Sequential
let sum: i32 = (0..1_000_000).map(|x| x * x).sum();
// Parallel (automatic work stealing)
let sum: i32 = (0..1_000_000).into_par_iter().map(|x| x * x).sum();
// Parallel with custom chunk size
let results: Vec<_> = data
.par_chunks(1000)
.map(|chunk| process_chunk(chunk))
.collect();
```
---
## Memory Layout
### Use Appropriate Integer Sizes
```rust
// If values are small, use smaller types
struct Item {
count: u8, // 0-255, not u64
flags: u8, // small enum
id: u32, // if 4 billion is enough
}
```
### Pack Structs Efficiently
```rust
// BAD: 24 bytes due to padding
struct Bad {
a: u8, // 1 byte + 7 padding
b: u64, // 8 bytes
c: u8, // 1 byte + 7 padding
}
// GOOD: 16 bytes (or use #[repr(packed)])
struct Good {
b: u64, // 8 bytes
a: u8, // 1 byte
c: u8, // 1 byte + 6 padding
}
```
### Box Large Values
```rust
// Large enum variants waste space
enum Message {
Quit,
Data([u8; 10000]), // all variants are 10000+ bytes
}
// Better: box the large variant
enum Message {
Quit,
Data(Box<[u8; 10000]>), // variants are pointer-sized
}
```
---
## Async Performance
### Avoid Blocking in Async
```rust
// BAD: blocks the executor
async fn bad() {
std::thread::sleep(Duration::from_secs(1)); // blocking!
std::fs::read_to_string("file.txt").unwrap(); // blocking!
}
// GOOD: use async versions
async fn good() {
tokio::time::sleep(Duration::from_secs(1)).await;
tokio::fs::read_to_string("file.txt").await.unwrap();
}
// For CPU work: spawn_blocking
async fn compute() -> i32 {
tokio::task::spawn_blocking(|| {
heavy_computation()
}).await.unwrap()
}
```
### Buffer Async I/O
```rust
use tokio::io::{AsyncBufReadExt, BufReader};
// BAD: many small reads
async fn bad(file: File) {
let mut byte = [0u8];
while file.read(&mut byte).await.unwrap() > 0 {
process(byte[0]);
}
}
// GOOD: buffered reading
async fn good(file: File) {
let reader = BufReader::new(file);
let mut lines = reader.lines();
while let Some(line) = lines.next_line().await.unwrap() {
process(&line);
}
}
```
---
## Release Build Optimization
### Cargo.toml Settings
```toml
[profile.release]
lto = true # Link-time optimization
codegen-units = 1 # Single codegen unit (slower compile, faster code)
panic = "abort" # Smaller binary, no unwinding
strip = true # Strip symbols
[profile.release-fast]
inherits = "release"
opt-level = 3 # Maximum optimization
[profile.release-small]
inherits = "release"
opt-level = "s" # Optimize for size
```
### Compile-Time Assertions
```rust
// Zero runtime cost
const _: () = assert!(std::mem::size_of::<MyStruct>() <= 64);
```
---
## Checklist
Before optimizing:
- [ ] Profile to find actual bottlenecks
- [ ] Have benchmarks to measure improvement
- [ ] Consider if optimization is worth complexity
Common wins:
- [ ] Reduce allocations (Cow, reuse buffers)
- [ ] Use appropriate collections
- [ ] Pre-allocate with_capacity
- [ ] Use iterators instead of indexing
- [ ] Enable LTO for release builds
- [ ] Use rayon for parallel workloads

View File

@@ -1,155 +0,0 @@
---
name: m11-ecosystem
description: "Use when integrating crates or ecosystem questions. Keywords: E0425, E0433, E0603, crate, cargo, dependency, feature flag, workspace, which crate to use, using external C libraries, creating Python extensions, PyO3, wasm, WebAssembly, bindgen, cbindgen, napi-rs, cannot find, private, crate recommendation, best crate for, Cargo.toml, features, crate 推荐, 依赖管理, 特性标志, 工作空间, Python 绑定"
---
# Ecosystem Integration
> **Layer 2: Design Choices**
## Core Question
**What's the right crate for this job, and how should it integrate?**
Before adding dependencies:
- Is there a standard solution?
- What's the maintenance status?
- What's the API stability?
---
## Integration Decision → Implementation
| Need | Choice | Crates |
|------|--------|--------|
| Serialization | Derive-based | serde, serde_json |
| Async runtime | tokio or async-std | tokio (most popular) |
| HTTP client | Ergonomic | reqwest |
| HTTP server | Modern | axum, actix-web |
| Database | SQL or ORM | sqlx, diesel |
| CLI parsing | Derive-based | clap |
| Error handling | App vs lib | anyhow, thiserror |
| Logging | Facade | tracing, log |
---
## Thinking Prompt
Before adding a dependency:
1. **Is it well-maintained?**
- Recent commits?
- Active issue response?
- Breaking changes frequency?
2. **What's the scope?**
- Do you need the full crate or just a feature?
- Can feature flags reduce bloat?
3. **How does it integrate?**
- Trait-based or concrete types?
- Sync or async?
- What bounds does it require?
---
## Trace Up ↑
To domain constraints (Layer 3):
```
"Which HTTP framework should I use?"
↑ Ask: What are the performance requirements?
↑ Check: domain-web (latency, throughput needs)
↑ Check: Team expertise (familiarity with framework)
```
| Question | Trace To | Ask |
|----------|----------|-----|
| Framework choice | domain-* | What constraints matter? |
| Library vs build | domain-* | What's the deployment model? |
| API design | domain-* | Who are the consumers? |
---
## Trace Down ↓
To implementation (Layer 1):
```
"Integrate external crate"
↓ m04-zero-cost: Trait bounds and generics
↓ m06-error-handling: Error type compatibility
"FFI integration"
↓ unsafe-checker: Safety requirements
↓ m12-lifecycle: Resource cleanup
```
---
## Quick Reference
### Language Interop
| Integration | Crate/Tool | Use Case |
|-------------|------------|----------|
| C/C++ → Rust | `bindgen` | Auto-generate bindings |
| Rust → C | `cbindgen` | Export C headers |
| Python ↔ Rust | `pyo3` | Python extensions |
| Node.js ↔ Rust | `napi-rs` | Node addons |
| WebAssembly | `wasm-bindgen` | Browser/WASI |
### Cargo Features
| Feature | Purpose |
|---------|---------|
| `[features]` | Optional functionality |
| `default = [...]` | Default features |
| `feature = "serde"` | Conditional deps |
| `[workspace]` | Multi-crate projects |
## Error Code Reference
| Error | Cause | Fix |
|-------|-------|-----|
| E0433 | Can't find crate | Add to Cargo.toml |
| E0603 | Private item | Check crate docs |
| Feature not enabled | Optional feature | Enable in `features` |
| Version conflict | Incompatible deps | `cargo update` or pin |
| Duplicate types | Different crate versions | Unify in workspace |
---
## Crate Selection Criteria
| Criterion | Good Sign | Warning Sign |
|-----------|-----------|--------------|
| Maintenance | Recent commits | Years inactive |
| Community | Active issues/PRs | No response |
| Documentation | Examples, API docs | Minimal docs |
| Stability | Semantic versioning | Frequent breaking |
| Dependencies | Minimal, well-known | Heavy, obscure |
---
## Anti-Patterns
| Anti-Pattern | Why Bad | Better |
|--------------|---------|--------|
| `extern crate` | Outdated (2018+) | Just `use` |
| `#[macro_use]` | Global pollution | Explicit import |
| Wildcard deps `*` | Unpredictable | Specific versions |
| Too many deps | Supply chain risk | Evaluate necessity |
| Vendoring everything | Maintenance burden | Trust crates.io |
---
## Related Skills
| When | See |
|------|-----|
| Error type design | m06-error-handling |
| Trait integration | m04-zero-cost |
| FFI safety | unsafe-checker |
| Resource management | m12-lifecycle |

View File

@@ -1,176 +0,0 @@
---
name: m12-lifecycle
description: "Use when designing resource lifecycles. Keywords: RAII, Drop, resource lifecycle, connection pool, lazy initialization, connection pool design, resource cleanup patterns, cleanup, scope, OnceCell, Lazy, once_cell, OnceLock, transaction, session management, when is Drop called, cleanup on error, guard pattern, scope guard, 资源生命周期, 连接池, 惰性初始化, 资源清理, RAII 模式"
---
# Resource Lifecycle
> **Layer 2: Design Choices**
## Core Question
**When should this resource be created, used, and cleaned up?**
Before implementing lifecycle:
- What's the resource's scope?
- Who owns the cleanup responsibility?
- What happens on error?
---
## Lifecycle Pattern → Implementation
| Pattern | When | Implementation |
|---------|------|----------------|
| RAII | Auto cleanup | `Drop` trait |
| Lazy init | Deferred creation | `OnceLock`, `LazyLock` |
| Pool | Reuse expensive resources | `r2d2`, `deadpool` |
| Guard | Scoped access | `MutexGuard` pattern |
| Scope | Transaction boundary | Custom struct + Drop |
---
## Thinking Prompt
Before designing lifecycle:
1. **What's the resource cost?**
- Cheap → create per use
- Expensive → pool or cache
- Global → lazy singleton
2. **What's the scope?**
- Function-local → stack allocation
- Request-scoped → passed or extracted
- Application-wide → static or Arc
3. **What about errors?**
- Cleanup must happen → Drop
- Cleanup is optional → explicit close
- Cleanup can fail → Result from close
---
## Trace Up ↑
To domain constraints (Layer 3):
```
"How should I manage database connections?"
↑ Ask: What's the connection cost?
↑ Check: domain-* (latency requirements)
↑ Check: Infrastructure (connection limits)
```
| Question | Trace To | Ask |
|----------|----------|-----|
| Connection pooling | domain-* | What's acceptable latency? |
| Resource limits | domain-* | What are infra constraints? |
| Transaction scope | domain-* | What must be atomic? |
---
## Trace Down ↓
To implementation (Layer 1):
```
"Need automatic cleanup"
↓ m02-resource: Implement Drop
↓ m01-ownership: Clear owner for cleanup
"Need lazy initialization"
↓ m03-mutability: OnceLock for thread-safe
↓ m07-concurrency: LazyLock for sync
"Need connection pool"
↓ m07-concurrency: Thread-safe pool
↓ m02-resource: Arc for sharing
```
---
## Quick Reference
| Pattern | Type | Use Case |
|---------|------|----------|
| RAII | `Drop` trait | Auto cleanup on scope exit |
| Lazy Init | `OnceLock`, `LazyLock` | Deferred initialization |
| Pool | `r2d2`, `deadpool` | Connection reuse |
| Guard | `MutexGuard` | Scoped lock release |
| Scope | Custom struct | Transaction boundaries |
## Lifecycle Events
| Event | Rust Mechanism |
|-------|----------------|
| Creation | `new()`, `Default` |
| Lazy Init | `OnceLock::get_or_init` |
| Usage | `&self`, `&mut self` |
| Cleanup | `Drop::drop()` |
## Pattern Templates
### RAII Guard
```rust
struct FileGuard {
path: PathBuf,
_handle: File,
}
impl Drop for FileGuard {
fn drop(&mut self) {
// Cleanup: remove temp file
let _ = std::fs::remove_file(&self.path);
}
}
```
### Lazy Singleton
```rust
use std::sync::OnceLock;
static CONFIG: OnceLock<Config> = OnceLock::new();
fn get_config() -> &'static Config {
CONFIG.get_or_init(|| {
Config::load().expect("config required")
})
}
```
---
## Common Errors
| Error | Cause | Fix |
|-------|-------|-----|
| Resource leak | Forgot Drop | Implement Drop or RAII wrapper |
| Double free | Manual memory | Let Rust handle |
| Use after drop | Dangling reference | Check lifetimes |
| E0509 move out of Drop | Moving owned field | `Option::take()` |
| Pool exhaustion | Not returned | Ensure Drop returns |
---
## Anti-Patterns
| Anti-Pattern | Why Bad | Better |
|--------------|---------|--------|
| Manual cleanup | Easy to forget | RAII/Drop |
| `lazy_static!` | External dep | `std::sync::OnceLock` |
| Global mutable state | Thread unsafety | `OnceLock` or proper sync |
| Forget to close | Resource leak | Drop impl |
---
## Related Skills
| When | See |
|------|-----|
| Smart pointers | m02-resource |
| Thread-safe init | m07-concurrency |
| Domain scopes | m09-domain |
| Error in cleanup | m06-error-handling |

View File

@@ -1,179 +0,0 @@
---
name: m13-domain-error
description: "Use when designing domain error handling. Keywords: domain error, error categorization, recovery strategy, retry, fallback, domain error hierarchy, user-facing vs internal errors, error code design, circuit breaker, graceful degradation, resilience, error context, backoff, retry with backoff, error recovery, transient vs permanent error, 领域错误, 错误分类, 恢复策略, 重试, 熔断器, 优雅降级"
---
# Domain Error Strategy
> **Layer 2: Design Choices**
## Core Question
**Who needs to handle this error, and how should they recover?**
Before designing error types:
- Is this user-facing or internal?
- Is recovery possible?
- What context is needed for debugging?
---
## Error Categorization
| Error Type | Audience | Recovery | Example |
|------------|----------|----------|---------|
| User-facing | End users | Guide action | `InvalidEmail`, `NotFound` |
| Internal | Developers | Debug info | `DatabaseError`, `ParseError` |
| System | Ops/SRE | Monitor/alert | `ConnectionTimeout`, `RateLimited` |
| Transient | Automation | Retry | `NetworkError`, `ServiceUnavailable` |
| Permanent | Human | Investigate | `ConfigInvalid`, `DataCorrupted` |
---
## Thinking Prompt
Before designing error types:
1. **Who sees this error?**
- End user → friendly message, actionable
- Developer → detailed, debuggable
- Ops → structured, alertable
2. **Can we recover?**
- Transient → retry with backoff
- Degradable → fallback value
- Permanent → fail fast, alert
3. **What context is needed?**
- Call chain → anyhow::Context
- Request ID → structured logging
- Input data → error payload
---
## Trace Up ↑
To domain constraints (Layer 3):
```
"How should I handle payment failures?"
↑ Ask: What are the business rules for retries?
↑ Check: domain-fintech (transaction requirements)
↑ Check: SLA (availability requirements)
```
| Question | Trace To | Ask |
|----------|----------|-----|
| Retry policy | domain-* | What's acceptable latency for retry? |
| User experience | domain-* | What message should users see? |
| Compliance | domain-* | What must be logged for audit? |
---
## Trace Down ↓
To implementation (Layer 1):
```
"Need typed errors"
↓ m06-error-handling: thiserror for library
↓ m04-zero-cost: Error enum design
"Need error context"
↓ m06-error-handling: anyhow::Context
↓ Logging: tracing with fields
"Need retry logic"
↓ m07-concurrency: async retry patterns
↓ Crates: tokio-retry, backoff
```
---
## Quick Reference
| Recovery Pattern | When | Implementation |
|------------------|------|----------------|
| Retry | Transient failures | exponential backoff |
| Fallback | Degraded mode | cached/default value |
| Circuit Breaker | Cascading failures | failsafe-rs |
| Timeout | Slow operations | `tokio::time::timeout` |
| Bulkhead | Isolation | separate thread pools |
## Error Hierarchy
```rust
#[derive(thiserror::Error, Debug)]
pub enum AppError {
// User-facing
#[error("Invalid input: {0}")]
Validation(String),
// Transient (retryable)
#[error("Service temporarily unavailable")]
ServiceUnavailable(#[source] reqwest::Error),
// Internal (log details, show generic)
#[error("Internal error")]
Internal(#[source] anyhow::Error),
}
impl AppError {
pub fn is_retryable(&self) -> bool {
matches!(self, Self::ServiceUnavailable(_))
}
}
```
## Retry Pattern
```rust
use tokio_retry::{Retry, strategy::ExponentialBackoff};
async fn with_retry<F, T, E>(f: F) -> Result<T, E>
where
F: Fn() -> impl Future<Output = Result<T, E>>,
E: std::fmt::Debug,
{
let strategy = ExponentialBackoff::from_millis(100)
.max_delay(Duration::from_secs(10))
.take(5);
Retry::spawn(strategy, || f()).await
}
```
---
## Common Mistakes
| Mistake | Why Wrong | Better |
|---------|-----------|--------|
| Same error for all | No actionability | Categorize by audience |
| Retry everything | Wasted resources | Only transient errors |
| Infinite retry | DoS self | Max attempts + backoff |
| Expose internal errors | Security risk | User-friendly messages |
| No context | Hard to debug | .context() everywhere |
---
## Anti-Patterns
| Anti-Pattern | Why Bad | Better |
|--------------|---------|--------|
| String errors | No structure | thiserror types |
| panic! for recoverable | Bad UX | Result with context |
| Ignore errors | Silent failures | Log or propagate |
| Box<dyn Error> everywhere | Lost type info | thiserror |
| Error in happy path | Performance | Early validation |
---
## Related Skills
| When | See |
|------|-----|
| Error handling basics | m06-error-handling |
| Retry implementation | m07-concurrency |
| Domain modeling | m09-domain |
| User-facing APIs | domain-* |

View File

@@ -1,176 +0,0 @@
---
name: m14-mental-model
description: "Use when learning Rust concepts. Keywords: mental model, how to think about ownership, understanding borrow checker, visualizing memory layout, analogy, misconception, explaining ownership, why does Rust, help me understand, confused about, learning Rust, explain like I'm, ELI5, intuition for, coming from Java, coming from Python, 心智模型, 如何理解所有权, 学习 Rust, Rust 入门, 为什么 Rust"
---
# Mental Models
> **Layer 2: Design Choices**
## Core Question
**What's the right way to think about this Rust concept?**
When learning or explaining Rust:
- What's the correct mental model?
- What misconceptions should be avoided?
- What analogies help understanding?
---
## Key Mental Models
| Concept | Mental Model | Analogy |
|---------|--------------|---------|
| Ownership | Unique key | Only one person has the house key |
| Move | Key handover | Giving away your key |
| `&T` | Lending for reading | Lending a book |
| `&mut T` | Exclusive editing | Only you can edit the doc |
| Lifetime `'a` | Valid scope | "Ticket valid until..." |
| `Box<T>` | Heap pointer | Remote control to TV |
| `Rc<T>` | Shared ownership | Multiple remotes, last turns off |
| `Arc<T>` | Thread-safe Rc | Remotes from any room |
---
## Coming From Other Languages
| From | Key Shift |
|------|-----------|
| Java/C# | Values are owned, not references by default |
| C/C++ | Compiler enforces safety rules |
| Python/Go | No GC, deterministic destruction |
| Functional | Mutability is safe via ownership |
| JavaScript | No null, use Option instead |
---
## Thinking Prompt
When confused about Rust:
1. **What's the ownership model?**
- Who owns this data?
- How long does it live?
- Who can access it?
2. **What guarantee is Rust providing?**
- No data races
- No dangling pointers
- No use-after-free
3. **What's the compiler telling me?**
- Error = violation of safety rule
- Solution = work with the rules
---
## Trace Up ↑
To design understanding (Layer 2):
```
"Why can't I do X in Rust?"
↑ Ask: What safety guarantee would be violated?
↑ Check: m01-m07 for the rule being enforced
↑ Ask: What's the intended design pattern?
```
---
## Trace Down ↓
To implementation (Layer 1):
```
"I understand the concept, now how do I implement?"
↓ m01-ownership: Ownership patterns
↓ m02-resource: Smart pointer choice
↓ m07-concurrency: Thread safety
```
---
## Common Misconceptions
| Error | Wrong Model | Correct Model |
|-------|-------------|---------------|
| E0382 use after move | GC cleans up | Ownership = unique key transfer |
| E0502 borrow conflict | Multiple writers OK | Only one writer at a time |
| E0499 multiple mut borrows | Aliased mutation | Exclusive access for mutation |
| E0106 missing lifetime | Ignoring scope | References have validity scope |
| E0507 cannot move from `&T` | Implicit clone | References don't own data |
## Deprecated Thinking
| Deprecated | Better |
|------------|--------|
| "Rust is like C++" | Different ownership model |
| "Lifetimes are GC" | Compile-time validity scope |
| "Clone solves everything" | Restructure ownership |
| "Fight the borrow checker" | Work with the compiler |
| "`unsafe` to avoid rules" | Understand safe patterns first |
---
## Ownership Visualization
```
Stack Heap
+----------------+ +----------------+
| main() | | |
| s1 ─────────────────────> │ "hello" |
| | | |
| fn takes(s) { | | |
| s2 (moved) ─────────────> │ "hello" |
| } | | (s1 invalid) |
+----------------+ +----------------+
After move: s1 is no longer valid
```
## Reference Visualization
```
+----------------+
| data: String |────────────> "hello"
+----------------+
│ &data (immutable borrow)
+------+------+
| reader1 reader2 (multiple OK)
+------+------+
+----------------+
| data: String |────────────> "hello"
+----------------+
│ &mut data (mutable borrow)
+------+
| writer (only one)
+------+
```
---
## Learning Path
| Stage | Focus | Skills |
|-------|-------|--------|
| Beginner | Ownership basics | m01-ownership, m14-mental-model |
| Intermediate | Smart pointers, error handling | m02, m06 |
| Advanced | Concurrency, unsafe | m07, unsafe-checker |
| Expert | Design patterns | m09-m15, domain-* |
---
## Related Skills
| When | See |
|------|-----|
| Ownership errors | m01-ownership |
| Smart pointers | m02-resource |
| Concurrency | m07-concurrency |
| Anti-patterns | m15-anti-pattern |

View File

@@ -1,286 +0,0 @@
# Thinking in Rust: Mental Models
## Core Mental Models
### 1. Ownership as Resource Management
```
Traditional: "Who has a pointer to this data?"
Rust: "Who OWNS this data and is responsible for freeing it?"
```
Key insight: Every value has exactly one owner. When the owner goes out of scope, the value is dropped.
```rust
{
let s = String::from("hello"); // s owns the String
// use s...
} // s goes out of scope, String is dropped (memory freed)
```
### 2. Borrowing as Temporary Access
```
Traditional: "I'll just read from this pointer"
Rust: "I'm borrowing this value, owner still responsible for it"
```
Key insight: Borrows are like library books - you can read them, but must return them.
```rust
fn print_length(s: &String) { // borrows s
println!("{}", s.len());
} // borrow ends, caller still owns s
let my_string = String::from("hello");
print_length(&my_string); // lend to function
println!("{}", my_string); // still have it
```
### 3. Lifetimes as Validity Scopes
```
Traditional: "Hope this pointer is still valid"
Rust: "Compiler tracks exactly how long references are valid"
```
Key insight: A reference can't outlive the data it points to.
```rust
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
// 'a means: the returned reference is valid as long as BOTH inputs are valid
if x.len() > y.len() { x } else { y }
}
```
---
## Shifting Perspectives
### From "Everything is a Reference" (Java/C#)
Java mental model:
```java
// Everything is implicitly a reference
User user = new User("Alice"); // user is a reference
List<User> users = new ArrayList<>();
users.add(user); // shares the reference
user.setName("Bob"); // affects the list too!
```
Rust mental model:
```rust
// Values are owned, sharing is explicit
let user = User::new("Alice"); // user is owned
let mut users = vec![];
users.push(user); // user moved into vec, can't use user anymore
// user.set_name("Bob"); // ERROR: user was moved
// If you need sharing:
use std::rc::Rc;
let user = Rc::new(User::new("Alice"));
let user2 = Rc::clone(&user); // explicit shared ownership
```
### From "Manual Memory Management" (C/C++)
C mental model:
```c
char* s = malloc(100);
// ... must remember to free(s) ...
// ... what if we return early? ...
// ... what if an exception occurs? ...
free(s);
```
Rust mental model:
```rust
let s = String::with_capacity(100);
// ... use s ...
// No need to free - Rust drops s automatically when scope ends
// Even with early returns, panics, or any control flow
```
### From "Garbage Collection" (Go/Python)
GC mental model:
```python
# Create objects, GC will figure it out
users = []
for name in names:
users.append(User(name))
# GC runs sometime later, when it feels like it
```
Rust mental model:
```rust
let users: Vec<User> = names
.iter()
.map(|name| User::new(name))
.collect();
// Memory is freed EXACTLY when users goes out of scope
// Deterministic, no GC pauses, no unpredictable memory usage
```
---
## Key Questions to Ask
### When Designing Functions
1. **Does this function need to own the data, or just read it?**
- Need to keep it: take ownership (`fn process(data: Vec<T>)`)
- Just reading: borrow (`fn process(data: &[T])`)
- Need to modify: mutable borrow (`fn process(data: &mut Vec<T>)`)
2. **Does the return value contain references to inputs?**
- Yes: need lifetime annotations
- No: lifetime elision usually works
### When Designing Structs
1. **Should this struct own its data or reference it?**
- Long-lived, independent: own (`name: String`)
- Short-lived view: reference (`name: &'a str`)
2. **Do multiple parts need to access the same data?**
- Single-threaded: `Rc<T>` or `Rc<RefCell<T>>`
- Multi-threaded: `Arc<T>` or `Arc<Mutex<T>>`
### When Hitting Borrow Checker Errors
1. **Am I trying to use a value after moving it?**
- Clone it, borrow it, or restructure the code
2. **Am I trying to have multiple mutable references?**
- Scope the mutations, use interior mutability, or redesign
3. **Does a reference outlive its source?**
- Return owned data instead, or use `'static`
---
## Common Patterns
### The Clone Escape Hatch
When fighting the borrow checker, `.clone()` often works:
```rust
// Can't do this - double borrow
let mut map = HashMap::new();
for key in map.keys() {
map.insert(key.clone(), process(key)); // ERROR: map borrowed twice
}
// Clone to escape
let keys: Vec<_> = map.keys().cloned().collect();
for key in keys {
map.insert(key.clone(), process(&key)); // OK
}
```
But ask: "Is there a better design?" Often, restructuring is better than cloning.
### The "Make It Own" Pattern
When lifetimes get complex, make the struct own its data:
```rust
// Complex: struct with references
struct Parser<'a> {
input: &'a str,
current: &'a str,
}
// Simpler: struct owns data
struct Parser {
input: String,
position: usize,
}
```
### The "Split the Borrow" Pattern
```rust
struct Data {
field_a: Vec<i32>,
field_b: Vec<i32>,
}
// Can't borrow self mutably twice
fn process(&mut self) {
// for a in &self.field_a {
// self.field_b.push(*a); // ERROR
// }
// Split the borrow
let Data { field_a, field_b } = self;
for a in field_a.iter() {
field_b.push(*a); // OK: separate borrows
}
}
```
---
## The Rust Way
### Embrace the Type System
```rust
// Don't: stringly-typed
fn connect(host: &str, port: &str) { ... }
connect("8080", "localhost"); // oops, wrong order
// Do: strongly-typed
struct Host(String);
struct Port(u16);
fn connect(host: Host, port: Port) { ... }
// connect(Port(8080), Host("localhost".into())); // compile error!
```
### Make Invalid States Unrepresentable
```rust
// Don't: runtime checks
struct Connection {
socket: Option<Socket>,
connected: bool,
}
// Do: types enforce states
enum Connection {
Disconnected,
Connected { socket: Socket },
}
```
### Let the Compiler Guide You
```rust
// Start with what you want
fn process(data: ???) -> ???
// Let compiler errors tell you:
// - What types are needed
// - What lifetimes are needed
// - What bounds are needed
// The error messages are documentation!
```
---
## Summary: The Rust Mental Model
1. **Values have owners** - exactly one at a time
2. **Borrowing is lending** - temporary access, owner retains responsibility
3. **Lifetimes are scopes** - compiler tracks validity
4. **Types encode constraints** - use them to prevent bugs
5. **The compiler is your friend** - work with it, not against it
When stuck:
- Clone to make progress
- Restructure to own instead of borrow
- Ask: "What is the compiler trying to tell me?"

View File

@@ -1,159 +0,0 @@
---
name: m15-anti-pattern
description: "Use when reviewing code for anti-patterns. Keywords: anti-pattern, common mistake, pitfall, code smell, bad practice, code review, is this an anti-pattern, better way to do this, common mistake to avoid, why is this bad, idiomatic way, beginner mistake, fighting borrow checker, clone everywhere, unwrap in production, should I refactor, 反模式, 常见错误, 代码异味, 最佳实践, 地道写法"
---
# Anti-Patterns
> **Layer 2: Design Choices**
## Core Question
**Is this pattern hiding a design problem?**
When reviewing code:
- Is this solving the symptom or the cause?
- Is there a more idiomatic approach?
- Does this fight or flow with Rust?
---
## Anti-Pattern → Better Pattern
| Anti-Pattern | Why Bad | Better |
|--------------|---------|--------|
| `.clone()` everywhere | Hides ownership issues | Proper references or ownership |
| `.unwrap()` in production | Runtime panics | `?`, `expect`, or handling |
| `Rc` when single owner | Unnecessary overhead | Simple ownership |
| `unsafe` for convenience | UB risk | Find safe pattern |
| OOP via `Deref` | Misleading API | Composition, traits |
| Giant match arms | Unmaintainable | Extract to methods |
| `String` everywhere | Allocation waste | `&str`, `Cow<str>` |
| Ignoring `#[must_use]` | Lost errors | Handle or `let _ =` |
---
## Thinking Prompt
When seeing suspicious code:
1. **Is this symptom or cause?**
- Clone to avoid borrow? → Ownership design issue
- Unwrap "because it won't fail"? → Unhandled case
2. **What would idiomatic code look like?**
- References instead of clones
- Iterators instead of index loops
- Pattern matching instead of flags
3. **Does this fight Rust?**
- Fighting borrow checker → restructure
- Excessive unsafe → find safe pattern
---
## Trace Up ↑
To design understanding:
```
"Why does my code have so many clones?"
↑ Ask: Is the ownership model correct?
↑ Check: m09-domain (data flow design)
↑ Check: m01-ownership (reference patterns)
```
| Anti-Pattern | Trace To | Question |
|--------------|----------|----------|
| Clone everywhere | m01-ownership | Who should own this data? |
| Unwrap everywhere | m06-error-handling | What's the error strategy? |
| Rc everywhere | m09-domain | Is ownership clear? |
| Fighting lifetimes | m09-domain | Should data structure change? |
---
## Trace Down ↓
To implementation (Layer 1):
```
"Replace clone with proper ownership"
↓ m01-ownership: Reference patterns
↓ m02-resource: Smart pointer if needed
"Replace unwrap with proper handling"
↓ m06-error-handling: ? operator
↓ m06-error-handling: expect with message
```
---
## Top 5 Beginner Mistakes
| Rank | Mistake | Fix |
|------|---------|-----|
| 1 | Clone to escape borrow checker | Use references |
| 2 | Unwrap in production | Propagate with `?` |
| 3 | String for everything | Use `&str` |
| 4 | Index loops | Use iterators |
| 5 | Fighting lifetimes | Restructure to own data |
## Code Smell → Refactoring
| Smell | Indicates | Refactoring |
|-------|-----------|-------------|
| Many `.clone()` | Ownership unclear | Clarify data flow |
| Many `.unwrap()` | Error handling missing | Add proper handling |
| Many `pub` fields | Encapsulation broken | Private + accessors |
| Deep nesting | Complex logic | Extract methods |
| Long functions | Multiple responsibilities | Split |
| Giant enums | Missing abstraction | Trait + types |
---
## Common Error Patterns
| Error | Anti-Pattern Cause | Fix |
|-------|-------------------|-----|
| E0382 use after move | Cloning vs ownership | Proper references |
| Panic in production | Unwrap everywhere | ?, matching |
| Slow performance | String for all text | &str, Cow |
| Borrow checker fights | Wrong structure | Restructure |
| Memory bloat | Rc/Arc everywhere | Simple ownership |
---
## Deprecated → Better
| Deprecated | Better |
|------------|--------|
| Index-based loops | `.iter()`, `.enumerate()` |
| `collect::<Vec<_>>()` then iterate | Chain iterators |
| Manual unsafe cell | `Cell`, `RefCell` |
| `mem::transmute` for casts | `as` or `TryFrom` |
| Custom linked list | `Vec`, `VecDeque` |
| `lazy_static!` | `std::sync::OnceLock` |
---
## Quick Review Checklist
- [ ] No `.clone()` without justification
- [ ] No `.unwrap()` in library code
- [ ] No `pub` fields with invariants
- [ ] No index loops when iterator works
- [ ] No `String` where `&str` suffices
- [ ] No ignored `#[must_use]` warnings
- [ ] No `unsafe` without SAFETY comment
- [ ] No giant functions (>50 lines)
---
## Related Skills
| When | See |
|------|-----|
| Ownership patterns | m01-ownership |
| Error handling | m06-error-handling |
| Mental models | m14-mental-model |
| Performance | m10-performance |

View File

@@ -1,421 +0,0 @@
# Common Rust Anti-Patterns & Mistakes
## Ownership Anti-Patterns
### 1. Clone Everything
```rust
// ANTI-PATTERN: clone to avoid borrow checker
fn process(data: Vec<String>) {
for item in data.clone() { // unnecessary clone
println!("{}", item);
}
use_data(data);
}
// BETTER: borrow when you don't need ownership
fn process(data: Vec<String>) {
for item in &data { // borrow instead
println!("{}", item);
}
use_data(data);
}
```
### 2. Unnecessary Box
```rust
// ANTI-PATTERN: boxing everything
fn get_value() -> Box<String> {
Box::new(String::from("hello"))
}
// BETTER: return value directly
fn get_value() -> String {
String::from("hello")
}
```
### 3. Holding References Too Long
```rust
// ANTI-PATTERN: borrow prevents mutation
let mut data = vec![1, 2, 3];
let first = &data[0];
data.push(4); // ERROR: data is borrowed
println!("{}", first);
// BETTER: scope the borrow
let mut data = vec![1, 2, 3];
let first = data[0]; // copy the value
data.push(4); // OK
println!("{}", first);
```
---
## Error Handling Anti-Patterns
### 4. Unwrap Everywhere
```rust
// ANTI-PATTERN: crashes on error
fn process_file(path: &str) {
let content = std::fs::read_to_string(path).unwrap();
let config: Config = toml::from_str(&content).unwrap();
}
// BETTER: propagate errors
fn process_file(path: &str) -> Result<Config, Error> {
let content = std::fs::read_to_string(path)?;
let config: Config = toml::from_str(&content)?;
Ok(config)
}
```
### 5. Ignoring Errors
```rust
// ANTI-PATTERN: silent failure
let _ = file.write_all(data);
// BETTER: handle or propagate
file.write_all(data)?;
// or at minimum, log the error
if let Err(e) = file.write_all(data) {
eprintln!("Warning: failed to write: {}", e);
}
```
### 6. Panic in Library Code
```rust
// ANTI-PATTERN: library panics
pub fn parse(input: &str) -> Data {
if input.is_empty() {
panic!("input cannot be empty");
}
// ...
}
// BETTER: return Result
pub fn parse(input: &str) -> Result<Data, ParseError> {
if input.is_empty() {
return Err(ParseError::EmptyInput);
}
// ...
}
```
---
## String Anti-Patterns
### 7. String Instead of &str
```rust
// ANTI-PATTERN: forces allocation
fn greet(name: String) {
println!("Hello, {}", name);
}
greet("world".to_string()); // allocation
// BETTER: accept &str
fn greet(name: &str) {
println!("Hello, {}", name);
}
greet("world"); // no allocation
```
### 8. Format for Simple Concatenation
```rust
// ANTI-PATTERN: format overhead
let greeting = format!("{}{}", "Hello, ", name);
// BETTER for simple cases: push_str
let mut greeting = String::from("Hello, ");
greeting.push_str(name);
// Or use + for String + &str
let greeting = String::from("Hello, ") + name;
```
### 9. Repeated String Operations
```rust
// ANTI-PATTERN: O(n²) allocations
let mut result = String::new();
for word in words {
result = result + word + " ";
}
// BETTER: join
let result = words.join(" ");
// Or with_capacity + push_str
let mut result = String::with_capacity(total_len);
for word in words {
result.push_str(word);
result.push(' ');
}
```
---
## Collection Anti-Patterns
### 10. Index Instead of Iterator
```rust
// ANTI-PATTERN: bounds checking overhead
for i in 0..vec.len() {
process(vec[i]);
}
// BETTER: iterator
for item in &vec {
process(item);
}
```
### 11. Collect Then Iterate
```rust
// ANTI-PATTERN: unnecessary allocation
let filtered: Vec<_> = items.iter().filter(|x| x.valid).collect();
for item in filtered {
process(item);
}
// BETTER: chain iterators
for item in items.iter().filter(|x| x.valid) {
process(item);
}
```
### 12. Wrong Collection Type
```rust
// ANTI-PATTERN: Vec for frequent membership checks
let allowed: Vec<&str> = vec!["a", "b", "c"];
if allowed.contains(&input) { ... } // O(n)
// BETTER: HashSet for membership
use std::collections::HashSet;
let allowed: HashSet<&str> = ["a", "b", "c"].into();
if allowed.contains(input) { ... } // O(1)
```
---
## Concurrency Anti-Patterns
### 13. Mutex for Read-Heavy Data
```rust
// ANTI-PATTERN: Mutex when mostly reading
let data = Arc::new(Mutex::new(config));
// All readers block each other
// BETTER: RwLock for read-heavy workloads
let data = Arc::new(RwLock::new(config));
// Multiple readers can proceed in parallel
```
### 14. Holding Lock Across Await
```rust
// ANTI-PATTERN: lock held across await
async fn bad() {
let guard = mutex.lock().unwrap();
some_async_op().await; // lock held!
use(guard);
}
// BETTER: scope the lock
async fn good() {
let value = {
let guard = mutex.lock().unwrap();
guard.clone()
}; // lock released
some_async_op().await;
use(value);
}
```
### 15. Blocking in Async
```rust
// ANTI-PATTERN: blocking call in async
async fn bad() {
std::thread::sleep(Duration::from_secs(1)); // blocks executor!
}
// BETTER: async sleep
async fn good() {
tokio::time::sleep(Duration::from_secs(1)).await;
}
// For CPU work: spawn_blocking
async fn compute() {
tokio::task::spawn_blocking(|| heavy_work()).await
}
```
---
## Type System Anti-Patterns
### 16. Stringly Typed
```rust
// ANTI-PATTERN: strings for everything
fn connect(host: &str, port: &str, timeout: &str) { ... }
connect("8080", "localhost", "30"); // wrong order!
// BETTER: strong types
struct Host(String);
struct Port(u16);
struct Timeout(Duration);
fn connect(host: Host, port: Port, timeout: Timeout) { ... }
```
### 17. Boolean Parameters
```rust
// ANTI-PATTERN: what does true mean?
fn fetch(url: &str, use_cache: bool, validate_ssl: bool) { ... }
fetch("https://...", true, false); // unclear
// BETTER: builder or named parameters
struct FetchOptions {
use_cache: bool,
validate_ssl: bool,
}
fn fetch(url: &str, options: FetchOptions) { ... }
fetch("https://...", FetchOptions {
use_cache: true,
validate_ssl: false,
});
```
### 18. Option<Option<T>>
```rust
// ANTI-PATTERN: nested Option
fn find(id: u32) -> Option<Option<User>> { ... }
// What does None vs Some(None) mean?
// BETTER: use Result or custom enum
enum FindResult {
Found(User),
NotFound,
Error(String),
}
```
---
## API Design Anti-Patterns
### 19. Taking Ownership Unnecessarily
```rust
// ANTI-PATTERN: takes ownership but doesn't need it
fn validate(config: Config) -> bool {
config.timeout > 0 && config.retries >= 0
}
// BETTER: borrow
fn validate(config: &Config) -> bool {
config.timeout > 0 && config.retries >= 0
}
```
### 20. Returning References to Temporaries
```rust
// ANTI-PATTERN: impossible lifetime
fn get_default() -> &str {
let s = String::from("default");
&s // ERROR: s is dropped
}
// BETTER: return owned
fn get_default() -> String {
String::from("default")
}
// Or return static
fn get_default() -> &'static str {
"default"
}
```
### 21. Overly Generic Functions
```rust
// ANTI-PATTERN: complex generics for simple function
fn process<T, U, V>(input: T) -> V
where
T: Into<U>,
U: AsRef<str> + Clone,
V: From<String>,
{ ... }
// BETTER: concrete types if generics not needed
fn process(input: &str) -> String { ... }
```
---
## Macro Anti-Patterns
### 22. Macro When Function Works
```rust
// ANTI-PATTERN: macro for simple operation
macro_rules! add {
($a:expr, $b:expr) => { $a + $b };
}
// BETTER: just use a function
fn add(a: i32, b: i32) -> i32 { a + b }
```
### 23. Complex Macro Without Tests
```rust
// ANTI-PATTERN: complex macro with no tests
macro_rules! define_api {
// ... 100 lines of macro code ...
}
// BETTER: test macro outputs
#[test]
fn test_macro_expansion() {
// Use cargo-expand or trybuild
}
```
---
## Quick Reference
| Anti-Pattern | Better Alternative |
|--------------|-------------------|
| Clone everywhere | Borrow when possible |
| Unwrap everywhere | Propagate with `?` |
| `String` parameters | `&str` parameters |
| Index loops | Iterator loops |
| Collect then process | Chain iterators |
| Mutex for reads | RwLock for read-heavy |
| Lock across await | Scope the lock |
| Blocking in async | spawn_blocking |
| Stringly typed | Strong types |
| Boolean params | Builders or enums |

View File

@@ -1,55 +0,0 @@
---
name: rust-daily
description: |
CRITICAL: Use for Rust news and daily/weekly/monthly reports. Triggers on:
rust news, rust daily, rust weekly, TWIR, rust blog,
Rust 日报, Rust 周报, Rust 新闻, Rust 动态
---
# Rust Daily Report
Fetch Rust community updates, filtered by time range.
## Data Sources
| Category | Sources |
|----------|---------|
| Ecosystem | Reddit r/rust, This Week in Rust |
| Official | blog.rust-lang.org, Inside Rust |
| Foundation | rustfoundation.org (news, blog, events) |
## Parameters
- `time_range`: day | week | month (default: week)
- `category`: all | ecosystem | official | foundation
## Execution
Read agent file then launch Task:
```
1. Read: ../../agents/rust-daily-reporter.md
2. Task(subagent_type: "general-purpose", run_in_background: false, prompt: <agent content>)
```
## Output Format
```markdown
# Rust {Weekly|Daily|Monthly} Report
**Time Range:** {start} - {end}
## Ecosystem
| Score | Title | Link |
## Official
| Date | Title | Summary |
## Foundation
| Date | Title | Summary |
```
## Validation
- Each source should have at least 1 result, otherwise mark "No updates"
- On fetch failure, retry with alternative tool

View File

@@ -1,171 +0,0 @@
---
name: rust-learner
description: "Use when asking about Rust versions or crate info. Keywords: latest version, what's new, changelog, Rust 1.x, Rust release, stable, nightly, crate info, crates.io, lib.rs, docs.rs, API documentation, crate features, dependencies, which crate, what version, Rust edition, edition 2021, edition 2024, cargo add, cargo update, 最新版本, 版本号, 稳定版, 最新, 哪个版本, crate 信息, 文档, 依赖, Rust 版本, 新特性, 有什么特性"
---
# Rust Learner
> **Version:** 1.1.0 | **Last Updated:** 2026-01-16
You are an expert at fetching Rust and crate information. Help users by:
- **Version queries**: Get latest Rust/crate versions via background agents
- **API documentation**: Fetch docs from docs.rs
- **Changelog**: Get Rust version features from releases.rs
**Primary skill for fetching Rust/crate information. All agents run in background.**
## CRITICAL: How to Launch Agents
**All agents MUST be launched via Task tool with these parameters:**
```
Task(
subagent_type: "general-purpose",
run_in_background: true,
prompt: <read from ../../agents/*.md file>
)
```
**Workflow:**
1. Read the agent prompt file: `../../agents/<agent-name>.md` (relative to this skill)
2. Launch Task with `run_in_background: true`
3. Continue with other work or wait for completion
4. Read results when agent completes
## Agent Routing Table
| Query Type | Agent File | Source |
|------------|------------|--------|
| Rust version features | `../../agents/rust-changelog.md` | releases.rs |
| Crate info/version | `../../agents/crate-researcher.md` | lib.rs, crates.io |
| **Std library docs** (Send, Sync, Arc, etc.) | `../../agents/std-docs-researcher.md` | doc.rust-lang.org |
| Third-party crate docs (tokio, serde, etc.) | `../../agents/docs-researcher.md` | docs.rs |
| Clippy lints | `../../agents/clippy-researcher.md` | rust-clippy docs |
| **Rust news/daily report** | `../../agents/rust-daily-reporter.md` | Reddit, TWIR, blogs |
### Choosing docs-researcher vs std-docs-researcher
| Query Pattern | Use Agent |
|---------------|-----------|
| `std::*`, `Send`, `Sync`, `Arc`, `Rc`, `Box`, `Vec`, `String` | `std-docs-researcher` |
| `tokio::*`, `serde::*`, any third-party crate | `docs-researcher` |
## Tool Chain
All agents use this tool chain (in order):
1. **actionbook MCP** (first - get pre-computed selectors)
- `mcp__actionbook__search_actions("site_name")` → get action ID
- `mcp__actionbook__get_action_by_id(id)` → get URL + selectors
2. **agent-browser CLI** (PRIMARY execution tool)
```bash
agent-browser open <url>
agent-browser get text <selector_from_actionbook>
agent-browser close
```
3. **WebFetch** (LAST RESORT only if agent-browser unavailable)
### Fallback Principle (CRITICAL)
```
actionbook → agent-browser → WebFetch (only if agent-browser unavailable)
```
**DO NOT:**
- Skip agent-browser because it's slower
- Use WebFetch as primary when agent-browser is available
- Block on WebFetch without trying agent-browser first
## Example: Crate Version Query
```
User: "tokio latest version"
Claude:
1. Read ../../agents/crate-researcher.md
2. Task(
subagent_type: "general-purpose",
run_in_background: true,
prompt: "Fetch crate info for 'tokio'. Use actionbook MCP to get lib.rs selectors, then agent-browser to fetch. Return: name, version, description, features."
)
3. Wait for agent or continue with other work
4. Summarize results to user
```
## Example: Third-Party Crate Documentation
```
User: "tokio::spawn documentation"
Claude:
1. Read ../../agents/docs-researcher.md
2. Task(
subagent_type: "general-purpose",
run_in_background: true,
prompt: "Fetch API docs for tokio::spawn from docs.rs. Use agent-browser first. Return: signature, description, examples."
)
3. Wait for agent
4. Summarize API to user
```
## Example: Std Library Documentation
```
User: "Send trait documentation"
Claude:
1. Read ../../agents/std-docs-researcher.md (NOT docs-researcher!)
2. Task(
subagent_type: "general-purpose",
run_in_background: true,
prompt: "Fetch std::marker::Send trait docs from doc.rust-lang.org. Use agent-browser first. Return: description, implementors, examples."
)
3. Wait for agent
4. Summarize trait to user
```
## Example: Rust Changelog Query
```
User: "What's new in Rust 1.85?"
Claude:
1. Read ../../agents/rust-changelog.md
2. Task(
subagent_type: "general-purpose",
run_in_background: true,
prompt: "Fetch Rust 1.85 changelog from releases.rs. Use actionbook MCP for selectors, agent-browser to fetch. Return: language features, library changes, stabilized APIs."
)
3. Wait for agent
4. Summarize features to user
```
## Deprecated Patterns
| Deprecated | Use Instead | Reason |
|------------|-------------|--------|
| WebSearch for crate info | Task + crate-researcher | Structured data |
| Direct WebFetch | Task + actionbook | Pre-computed selectors |
| Foreground agent execution | `run_in_background: true` | Non-blocking |
| Guessing version numbers | Always use agents | Prevents misinformation |
## Error Handling
| Error | Cause | Solution |
|-------|-------|----------|
| actionbook unavailable | MCP not configured | Fall back to WebFetch |
| agent-browser not found | CLI not installed | Fall back to WebFetch |
| Agent timeout | Site slow/down | Retry or inform user |
| Empty results | Selector mismatch | Report and use WebFetch fallback |
## Proactive Triggering
This skill triggers AUTOMATICALLY when:
- Any Rust crate name mentioned (tokio, serde, axum, sqlx, etc.)
- Questions about "latest", "new", "version", "changelog"
- API documentation requests
- Dependency/feature questions
**DO NOT use WebSearch for Rust crate info. Launch background agents instead.**

View File

@@ -1,341 +0,0 @@
---
name: rust-router
description: "CRITICAL: Use for ALL Rust questions including errors, design, and coding.
Triggers on: Rust, cargo, rustc, crate, Cargo.toml,
意图分析, 问题分析, 语义分析, analyze intent, question analysis,
compile error, borrow error, lifetime error, ownership error, type error, trait error,
value moved, cannot borrow, does not live long enough, mismatched types, not satisfied,
E0382, E0597, E0277, E0308, E0499, E0502, E0596,
async, await, Send, Sync, tokio, unsafe, FFI, concurrency, error handling,
编译错误, compile error, 所有权, ownership, 借用, borrow, 生命周期, lifetime, 类型错误, type error,
异步, async, 并发, concurrency, 错误处理, error handling,
问题, problem, question, 怎么用, how to use, 如何, how to, 为什么, why,
什么是, what is, 帮我写, help me write, 实现, implement, 解释, explain, 区别, difference, 最佳实践, best practice"
globs: ["**/Cargo.toml", "**/*.rs"]
---
# Rust Question Router
> **Version:** 2.0.0 | **Last Updated:** 2026-01-17
>
> **New in v2.0:** Meta-Cognition Routing - Three-layer cognitive model for deeper answers
## Meta-Cognition Framework
### Core Principle
**Don't answer directly. Trace through the cognitive layers first.**
```
Layer 3: Domain Constraints (WHY)
├── Business rules, regulatory requirements
├── domain-fintech, domain-web, domain-cli, etc.
└── "Why is it designed this way?"
Layer 2: Design Choices (WHAT)
├── Architecture patterns, DDD concepts
├── m09-m15 skills
└── "What pattern should I use?"
Layer 1: Language Mechanics (HOW)
├── Ownership, borrowing, lifetimes, traits
├── m01-m07 skills
└── "How do I implement this in Rust?"
```
### Routing by Entry Point
| User Signal | Entry Layer | Direction | First Skill |
|-------------|-------------|-----------|-------------|
| E0xxx error | Layer 1 | Trace UP ↑ | m01-m07 |
| Compile error | Layer 1 | Trace UP ↑ | Error table below |
| "How to design..." | Layer 2 | Check L3, then DOWN ↓ | m09-domain |
| "Building [domain] app" | Layer 3 | Trace DOWN ↓ | domain-* |
| "Best practice..." | Layer 2 | Both directions | m09-m15 |
| Performance issue | Layer 1 → 2 | UP then DOWN | m10-performance |
### CRITICAL: Dual-Skill Loading
**When domain keywords are present, you MUST load BOTH skills:**
| Domain Keywords | L1 Skill | L3 Skill |
|-----------------|----------|----------|
| Web API, HTTP, axum, handler | m07-concurrency | **domain-web** |
| 交易, 支付, trading, payment | m01-ownership | **domain-fintech** |
| CLI, terminal, clap | m07-concurrency | **domain-cli** |
| kubernetes, grpc, microservice | m07-concurrency | **domain-cloud-native** |
| embedded, no_std, MCU | m02-resource | **domain-embedded** |
**Example**: "Web API 报错 Rc cannot be sent"
- Load: m07-concurrency (L1 - Send/Sync error)
- Load: domain-web (L3 - Web state management)
- Answer must include both layers
### Trace Examples
```
User: "My trading system reports E0382"
1. Entry: Layer 1 (E0382 = ownership error)
2. Load: m01-ownership skill
3. Trace UP: What design led to this?
4. Check: domain-fintech (trading = immutable audit data)
5. Answer: Don't clone, use Arc<T> for shared immutable data
```
```
User: "How should I handle user authentication?"
1. Entry: Layer 2 (design question)
2. Trace UP to Layer 3: domain-web constraints
3. Load: domain-web skill (security, stateless HTTP)
4. Trace DOWN: m06-error-handling, m07-concurrency
5. Answer: JWT with proper error types, async handlers
```
---
## INSTRUCTIONS FOR CLAUDE
### Default Project Settings (When Creating Rust Code)
When creating new Rust projects or Cargo.toml files, ALWAYS use:
```toml
[package]
edition = "2024" # ALWAYS use latest stable edition
rust-version = "1.85"
[lints.rust]
unsafe_code = "warn"
[lints.clippy]
all = "warn"
pedantic = "warn"
```
**Rules:**
- ALWAYS use `edition = "2024"` (not 2021 or earlier)
- Include `rust-version` for MSRV clarity
- Enable clippy lints by default
- DO NOT use WebSearch for Rust questions - use skills and agents
### Meta-Cognition Routing Process
1. **Identify Entry Layer**
- E0xxx errors → Layer 1
- Design questions → Layer 2
- Domain-specific → Layer 3
2. **Load Appropriate Skill**
- Read the skill file for context
- Note the "Trace Up" and "Trace Down" sections
3. **Trace Through Layers**
- Don't stop at surface-level fix
- Ask "Why?" to trace up
- Ask "How?" to trace down
4. **Answer with Context**
- Include the reasoning chain
- Reference which layers/skills informed the answer
### When User Requests Intent Analysis
User may say: "analyze this question" / "what type of problem is this" / "analyze intent"
**Execute these steps:**
1. **Extract Keywords** - Identify Rust concepts, error codes, crate names
2. **Identify Entry Layer** - Which cognitive layer is this?
3. **Map to Skills** - Which m0x/m1x/domain skills apply?
4. **Report Analysis** - Tell user the layers and suggested trace
5. **Invoke Skill** - Load and apply the matched skill
---
## Layer 1 Skills (Language Mechanics)
| Pattern | Category | Route To |
|---------|----------|----------|
| move, borrow, lifetime, E0382, E0597 | m01 | m01-ownership |
| Box, Rc, Arc, RefCell, Cell | m02 | m02-resource |
| mut, interior mutability, E0499, E0502, E0596 | m03 | m03-mutability |
| generic, trait, inline, monomorphization | m04 | m04-zero-cost |
| type state, phantom, newtype | m05 | m05-type-driven |
| Result, Error, panic, ?, anyhow, thiserror | m06 | m06-error-handling |
| Send, Sync, thread, async, channel | m07 | m07-concurrency |
| unsafe, FFI, extern, raw pointer, transmute | - | **unsafe-checker** |
## Layer 2 Skills (Design Choices)
| Pattern | Category | Route To |
|---------|----------|----------|
| domain model, business logic | m09 | m09-domain |
| performance, optimization, benchmark | m10 | m10-performance |
| integration, interop, bindings | m11 | m11-ecosystem |
| resource lifecycle, RAII, Drop | m12 | m12-lifecycle |
| domain error, recovery strategy | m13 | m13-domain-error |
| mental model, how to think | m14 | m14-mental-model |
| anti-pattern, common mistake, pitfall | m15 | m15-anti-pattern |
## Layer 3 Skills (Domain Constraints)
| Domain Keywords | Route To |
|-----------------|----------|
| fintech, trading, decimal, currency | domain-fintech |
| ml, tensor, model, inference | domain-ml |
| kubernetes, docker, grpc, microservice | domain-cloud-native |
| embedded, sensor, mqtt, iot | domain-iot |
| web server, HTTP, REST, axum, actix | domain-web |
| CLI, command line, clap, terminal | domain-cli |
| no_std, microcontroller, firmware | domain-embedded |
---
## Error Code Routing
| Error Code | Layer | Route To | Common Cause |
|------------|-------|----------|--------------|
| E0382 | L1 | m01-ownership | Use of moved value |
| E0597 | L1 | m01-ownership | Lifetime too short |
| E0506 | L1 | m01-ownership | Cannot assign to borrowed |
| E0507 | L1 | m01-ownership | Cannot move out of borrowed |
| E0515 | L1 | m01-ownership | Return local reference |
| E0716 | L1 | m01-ownership | Temporary value dropped |
| E0106 | L1 | m01-ownership | Missing lifetime specifier |
| E0596 | L1 | m03-mutability | Cannot borrow as mutable |
| E0499 | L1 | m03-mutability | Multiple mutable borrows |
| E0502 | L1 | m03-mutability | Borrow conflict |
| E0277 | L1 | m04/m07 | Trait bound not satisfied |
| E0308 | L1 | m04-zero-cost | Type mismatch |
| E0599 | L1 | m04-zero-cost | No method found |
| E0038 | L1 | m04-zero-cost | Trait not object-safe |
| E0433 | L1 | m11-ecosystem | Cannot find crate/module |
---
## Unsafe-Specific Routing
For **detailed unsafe rules**, route to `unsafe-checker` skill:
| Pattern | Route To |
|---------|----------|
| unsafe code review, SAFETY comment | **unsafe-checker** |
| FFI, extern "C", C interop, libc | **unsafe-checker** |
| raw pointer, *mut, *const, NonNull | **unsafe-checker** |
| transmute, union, repr(C) | **unsafe-checker** |
| MaybeUninit, uninitialized memory | **unsafe-checker** |
| panic safety, double-free | **unsafe-checker** |
| Send impl, Sync impl, manual auto-traits | **unsafe-checker** |
---
## Functional Routing Table
| Pattern | Route To | Action |
|---------|----------|--------|
| latest version, what's new | **rust-learner** | Use agents |
| API, docs, documentation | **docs-researcher** | Use agent |
| Cargo.toml, dependencies | **dynamic-skills** | Suggest `/sync-crate-skills` |
| code style, naming, clippy | **coding-guidelines** | Read skill |
| unsafe code, FFI | **unsafe-checker** | Read skill |
| code review | **os-checker** | Suggest `/rust-review` |
---
## Priority Order
1. **Identify cognitive layer** (L1/L2/L3)
2. **Load entry skill** (m0x/m1x/domain)
3. **Trace through layers** (UP or DOWN)
4. **Cross-reference skills** as indicated in "Trace" sections
5. **Answer with reasoning chain**
## Skill File Paths
### Meta-Cognition Framework
```
_meta/reasoning-framework.md # How to trace through layers
_meta/layer-definitions.md # Layer definitions
_meta/externalization.md # Cognitive externalization
_meta/error-protocol.md # 3-Strike escalation rule
_meta/hooks-patterns.md # Automatic triggers
```
### Layer 1 Skills (Language Mechanics)
```
skills/m01-ownership/SKILL.md
skills/m02-resource/SKILL.md
skills/m03-mutability/SKILL.md
skills/m04-zero-cost/SKILL.md
skills/m05-type-driven/SKILL.md
skills/m06-error-handling/SKILL.md
skills/m07-concurrency/SKILL.md
```
### Layer 2 Skills (Design Choices)
```
skills/m09-domain/SKILL.md
skills/m10-performance/SKILL.md
skills/m11-ecosystem/SKILL.md
skills/m12-lifecycle/SKILL.md
skills/m13-domain-error/SKILL.md
skills/m14-mental-model/SKILL.md
skills/m15-anti-pattern/SKILL.md
```
### Layer 3 Skills (Domain Constraints)
```
skills/domain-fintech/SKILL.md
skills/domain-ml/SKILL.md
skills/domain-cloud-native/SKILL.md
skills/domain-iot/SKILL.md
skills/domain-web/SKILL.md
skills/domain-cli/SKILL.md
skills/domain-embedded/SKILL.md
```
---
## OS-Checker Integration
For code review and security auditing:
| Use Case | Command | Tools |
|----------|---------|-------|
| Daily check | `/rust-review` | clippy |
| Security audit | `/audit security` | cargo audit, geiger |
| Unsafe audit | `/audit safety` | miri, rudra |
| Concurrency audit | `/audit concurrency` | lockbud |
| Full audit | `/audit full` | all os-checker tools |
---
## Workflow Example with Meta-Cognition
```
User: "Why am I getting E0382 in my trading system?"
Analysis:
1. Entry: Layer 1 (E0382 = ownership/move error)
2. Load: m01-ownership skill
3. Context: "trading system" → domain-fintech
Trace UP ↑:
- E0382 in trading context
- Check domain-fintech: "immutable audit records"
- Finding: Trading data should be shared, not moved
Response:
"E0382 indicates a value was moved when still needed.
In a trading system (domain-fintech), transaction records
should be immutable and shareable for audit purposes.
Instead of cloning, consider:
- Arc<TradeRecord> for shared immutable access
- This aligns with financial audit requirements
See: m01-ownership (Trace Up section),
domain-fintech (Audit Requirements)"
```

View File

@@ -1,125 +0,0 @@
---
name: rust-skill-creator
description: "Use when creating skills for Rust crates or std library documentation. Keywords: create rust skill, create crate skill, create std skill, 创建 rust skill, 创建 crate skill, 创建 std skill, 动态 rust skill, 动态 crate skill, skill for tokio, skill for serde, skill for axum, generate rust skill, rust 技能, crate 技能, 从文档创建skill, from docs create skill"
---
# Rust Skill Creator
> Create dynamic skills for Rust crates and std library documentation.
## When to Use
This skill handles requests to create skills for:
- Third-party crates (tokio, serde, axum, etc.)
- Rust standard library (std::sync, std::marker, etc.)
- Any Rust documentation URL
## Workflow
### 1. Identify the Target
| User Request | Target Type | URL Pattern |
|--------------|-------------|-------------|
| "create tokio skill" | Third-party crate | `docs.rs/tokio/latest/tokio/` |
| "create Send trait skill" | Std library | `doc.rust-lang.org/std/marker/trait.Send.html` |
| "create skill from URL" + URL | Custom URL | User-provided URL |
### 2. Execute the Command
Use the `/create-llms-for-skills` command:
```
/create-llms-for-skills <url> [requirements]
```
**Examples:**
```bash
# For third-party crate
/create-llms-for-skills https://docs.rs/tokio/latest/tokio/
# For std library
/create-llms-for-skills https://doc.rust-lang.org/std/marker/trait.Send.html
# With specific requirements
/create-llms-for-skills https://docs.rs/axum/latest/axum/ "Focus on routing and extractors"
```
### 3. Follow-up with Skill Creation
After llms.txt is generated, use:
```
/create-skills-via-llms <crate_name> <llms_path> [version]
```
## URL Construction Helper
| Target | URL Template |
|--------|--------------|
| Crate overview | `https://docs.rs/{crate}/latest/{crate}/` |
| Crate module | `https://docs.rs/{crate}/latest/{crate}/{module}/` |
| Std trait | `https://doc.rust-lang.org/std/{module}/trait.{Name}.html` |
| Std struct | `https://doc.rust-lang.org/std/{module}/struct.{Name}.html` |
| Std module | `https://doc.rust-lang.org/std/{module}/index.html` |
## Common Std Library Paths
| Item | Path |
|------|------|
| Send, Sync, Copy, Clone | `std/marker/trait.{Name}.html` |
| Arc, Mutex, RwLock | `std/sync/struct.{Name}.html` |
| Rc, Weak | `std/rc/struct.{Name}.html` |
| RefCell, Cell | `std/cell/struct.{Name}.html` |
| Box | `std/boxed/struct.Box.html` |
| Vec | `std/vec/struct.Vec.html` |
| String | `std/string/struct.String.html` |
| Option | `std/option/enum.Option.html` |
| Result | `std/result/enum.Result.html` |
## Example Interactions
### Example 1: Create Crate Skill
```
User: "Create a dynamic skill for tokio"
Claude:
1. Identify: Third-party crate "tokio"
2. Execute: /create-llms-for-skills https://docs.rs/tokio/latest/tokio/
3. Wait for llms.txt generation
4. Execute: /create-skills-via-llms tokio ~/tmp/{timestamp}-tokio-llms.txt
```
### Example 2: Create Std Library Skill
```
User: "Create a skill for Send and Sync traits"
Claude:
1. Identify: Std library traits
2. Execute: /create-llms-for-skills https://doc.rust-lang.org/std/marker/trait.Send.html https://doc.rust-lang.org/std/marker/trait.Sync.html
3. Wait for llms.txt generation
4. Execute: /create-skills-via-llms std-marker ~/tmp/{timestamp}-std-marker-llms.txt
```
### Example 3: Custom URL
```
User: "Create skill from https://docs.rs/sqlx/latest/sqlx/"
Claude:
1. Identify: User-provided URL
2. Execute: /create-llms-for-skills https://docs.rs/sqlx/latest/sqlx/
3. Follow standard workflow
```
## DO NOT
- Use `best-skill-creator` for Rust-related skill creation
- Skip the `/create-llms-for-skills` step
- Guess documentation URLs without verification
## Output Location
All generated skills are saved to: `~/.claude/skills/`

View File

@@ -1,136 +0,0 @@
# Unsafe Checker - Quick Reference
**Auto-generated from rules/**
## Rule Summary by Section
### General Principles (3 rules)
| ID | Level | Title |
|----|-------|-------|
| general-01 | P | Do Not Abuse Unsafe to Escape Compiler Safety Checks |
| general-02 | P | Do Not Blindly Use Unsafe for Performance |
| general-03 | G | Do Not Create Aliases for Types/Methods Named "Unsafe" |
### Safety Abstraction (11 rules)
| ID | Level | Title |
|----|-------|-------|
| safety-01 | P | Be Aware of Memory Safety Issues from Panics |
| safety-02 | P | Unsafe Code Authors Must Verify Safety Invariants |
| safety-03 | P | Do Not Expose Uninitialized Memory in Public APIs |
| safety-04 | P | Avoid Double-Free from Panic Safety Issues |
| safety-05 | P | Consider Safety When Manually Implementing Auto Traits |
| safety-06 | P | Do Not Expose Raw Pointers in Public APIs |
| safety-07 | P | Provide Unsafe Counterparts for Performance Alongside Safe Methods |
| safety-08 | P | Mutable Return from Immutable Parameter is Wrong |
| safety-09 | P | Add SAFETY Comment Before Any Unsafe Block |
| safety-10 | G | Add Safety Section in Docs for Public Unsafe Functions |
| safety-11 | G | Use assert! Instead of debug_assert! in Unsafe Functions |
### Raw Pointers (6 rules)
| ID | Level | Title |
|----|-------|-------|
| ptr-01 | P | Do Not Share Raw Pointers Across Threads |
| ptr-02 | P | Prefer NonNull<T> Over *mut T |
| ptr-03 | P | Use PhantomData<T> for Variance and Ownership |
| ptr-04 | G | Do Not Dereference Pointers Cast to Misaligned Types |
| ptr-05 | G | Do Not Manually Convert Immutable Pointer to Mutable |
| ptr-06 | G | Prefer pointer::cast Over `as` for Pointer Casting |
### Union (2 rules)
| ID | Level | Title |
|----|-------|-------|
| union-01 | P | Avoid Union Except for C Interop |
| union-02 | P | Do Not Use Union Variants Across Different Lifetimes |
### Memory Layout (6 rules)
| ID | Level | Title |
|----|-------|-------|
| mem-01 | P | Choose Appropriate Data Layout for Struct/Tuple/Enum |
| mem-02 | P | Do Not Modify Memory Variables of Other Processes |
| mem-03 | P | Do Not Let String/Vec Auto-Drop Other Process's Memory |
| mem-04 | P | Prefer Reentrant Versions of C-API or Syscalls |
| mem-05 | P | Use Third-Party Crates for Bitfields |
| mem-06 | G | Use MaybeUninit<T> for Uninitialized Memory |
### FFI (18 rules)
| ID | Level | Title |
|----|-------|-------|
| ffi-01 | P | Avoid Passing Strings Directly to C |
| ffi-02 | P | Read Documentation Carefully for std::ffi Types |
| ffi-03 | P | Implement Drop for Wrapped C Pointers |
| ffi-04 | P | Handle Panics When Crossing FFI Boundaries |
| ffi-05 | P | Use Portable Type Aliases from std or libc |
| ffi-06 | P | Ensure C-ABI String Compatibility |
| ffi-07 | P | Do Not Implement Drop for Types Passed to External Code |
| ffi-08 | P | Handle Errors Properly in FFI |
| ffi-09 | P | Use References Instead of Raw Pointers in Safe Wrappers |
| ffi-10 | P | Exported Functions Must Be Thread-Safe |
| ffi-11 | P | Be Careful with repr(packed) Field References |
| ffi-12 | P | Document Invariant Assumptions for C Parameters |
| ffi-13 | P | Ensure Consistent Data Layout for Custom Types |
| ffi-14 | P | Types in FFI Should Have Stable Layout |
| ffi-15 | P | Validate Non-Robust External Values |
| ffi-16 | P | Separate Data and Code for Closures to C |
| ffi-17 | P | Use Opaque Types Instead of c_void |
| ffi-18 | P | Avoid Passing Trait Objects to C |
### I/O Safety (1 rule)
| ID | Level | Title |
|----|-------|-------|
| io-01 | P | Ensure I/O Safety When Using Raw Handles |
## Clippy Lint Mapping
| Clippy Lint | Rule | Category |
|-------------|------|----------|
| `undocumented_unsafe_blocks` | safety-09 | SAFETY comments |
| `missing_safety_doc` | safety-10 | Safety docs |
| `panic_in_result_fn` | safety-01, ffi-04 | Panic safety |
| `non_send_fields_in_send_ty` | safety-05 | Send/Sync |
| `uninit_assumed_init` | safety-03 | Initialization |
| `uninit_vec` | mem-06 | Initialization |
| `mut_from_ref` | safety-08 | Aliasing |
| `cast_ptr_alignment` | ptr-04 | Alignment |
| `cast_ref_to_mut` | ptr-05 | Aliasing |
| `ptr_as_ptr` | ptr-06 | Pointer casting |
| `unaligned_references` | ffi-11 | Packed structs |
| `debug_assert_with_mut_call` | safety-11 | Assertions |
## Quick Decision Tree
```
Writing unsafe code?
├─ FFI with C?
│ └─ See ffi-* rules
├─ Raw pointers?
│ └─ See ptr-* rules
├─ Manual Send/Sync?
│ └─ See safety-05
├─ MaybeUninit/uninitialized?
│ └─ See safety-03, mem-06
└─ Performance optimization?
└─ See general-02, safety-07
```
## Essential Checklist
Before every unsafe block:
- [ ] SAFETY comment present
- [ ] Invariants documented
- [ ] Pointer validity checked
- [ ] Aliasing rules followed
- [ ] Panic safety considered
- [ ] Tested with Miri
## Resources
- `checklists/before-unsafe.md` - Pre-writing checklist
- `checklists/review-unsafe.md` - Code review checklist
- `checklists/common-pitfalls.md` - Common bugs and fixes
- `examples/safe-abstraction.md` - Safe wrapper patterns
- `examples/ffi-patterns.md` - FFI best practices

View File

@@ -1,72 +0,0 @@
---
name: unsafe-checker
description: "Use when reviewing unsafe code or writing FFI. Keywords: unsafe, raw pointer, FFI, extern, transmute, *mut, *const, union, #[repr(C)], libc, std::ffi, MaybeUninit, NonNull, PhantomData, Send, Sync, SAFETY comment, soundness, undefined behavior, UB, how to call C functions, safe wrapper for unsafe code, when is unsafe necessary, memory layout, bindgen, cbindgen, CString, CStr, invariant, 安全抽象, 裸指针, 外部函数接口, 内存布局, 不安全代码, FFI 绑定, 未定义行为"
globs: ["**/*.rs"]
---
# Unsafe Rust Checker
## When Unsafe is Valid
| Use Case | Example |
|----------|---------|
| FFI | Calling C functions |
| Low-level abstractions | Implementing `Vec`, `Arc` |
| Performance | Measured bottleneck with safe alternative too slow |
**NOT valid:** Escaping borrow checker without understanding why.
## Required Documentation
```rust
// SAFETY: <why this is safe>
unsafe { ... }
/// # Safety
/// <caller requirements>
pub unsafe fn dangerous() { ... }
```
## Quick Reference
| Operation | Safety Requirements |
|-----------|---------------------|
| `*ptr` deref | Valid, aligned, initialized |
| `&*ptr` | + No aliasing violations |
| `transmute` | Same size, valid bit pattern |
| `extern "C"` | Correct signature, ABI |
| `static mut` | Synchronization guaranteed |
| `impl Send/Sync` | Actually thread-safe |
## Common Errors
| Error | Fix |
|-------|-----|
| Null pointer deref | Check for null before deref |
| Use after free | Ensure lifetime validity |
| Data race | Add proper synchronization |
| Alignment violation | Use `#[repr(C)]`, check alignment |
| Invalid bit pattern | Use `MaybeUninit` |
| Missing SAFETY comment | Add `// SAFETY:` |
## Deprecated → Better
| Deprecated | Use Instead |
|------------|-------------|
| `mem::uninitialized()` | `MaybeUninit<T>` |
| `mem::zeroed()` for refs | `MaybeUninit<T>` |
| Raw pointer arithmetic | `NonNull<T>`, `ptr::add` |
| `CString::new().unwrap().as_ptr()` | Store `CString` first |
| `static mut` | `AtomicT` or `Mutex` |
| Manual extern | `bindgen` |
## FFI Crates
| Direction | Crate |
|-----------|-------|
| C → Rust | bindgen |
| Rust → C | cbindgen |
| Python | PyO3 |
| Node.js | napi-rs |
Claude knows unsafe Rust. Focus on SAFETY comments and soundness.

View File

@@ -1,115 +0,0 @@
# Checklist: Before Writing Unsafe Code
Use this checklist before writing any `unsafe` block or `unsafe fn`.
## 1. Do You Really Need Unsafe?
- [ ] Have you tried all safe alternatives?
- [ ] Can you restructure the code to satisfy the borrow checker?
- [ ] Would interior mutability (`Cell`, `RefCell`, `Mutex`) solve the problem?
- [ ] Is there a safe crate that already does this?
- [ ] Is the performance gain (if any) worth the safety risk?
**If you answered "no" to all, proceed with unsafe.**
## 2. What Unsafe Operation Do You Need?
Identify which specific unsafe operation you're performing:
- [ ] Dereferencing a raw pointer (`*const T`, `*mut T`)
- [ ] Calling an `unsafe` function
- [ ] Accessing a mutable static variable
- [ ] Implementing an unsafe trait (`Send`, `Sync`, etc.)
- [ ] Accessing fields of a `union`
- [ ] Using `extern "C"` functions (FFI)
## 3. Safety Invariants
For each unsafe operation, document the invariants:
### For Pointer Dereference:
- [ ] Is the pointer non-null?
- [ ] Is the pointer properly aligned for the type?
- [ ] Does the pointer point to valid, initialized memory?
- [ ] Is the memory not being mutated by other code?
- [ ] Will the memory remain valid for the entire duration of use?
### For Mutable Aliasing:
- [ ] Are you creating multiple mutable references to the same memory?
- [ ] Is there any possibility of aliasing `&mut` and `&`?
- [ ] Have you verified no other code can access this memory?
### For FFI:
- [ ] Is the function signature correct (types, ABI)?
- [ ] Are you handling potential null pointers?
- [ ] Are you handling potential panics (catch_unwind)?
- [ ] Is memory ownership clear (who allocates, who frees)?
### For Send/Sync:
- [ ] Is concurrent access properly synchronized?
- [ ] Are there any data races possible?
- [ ] Does the type truly satisfy the trait requirements?
## 4. Panic Safety
- [ ] What happens if this code panics at any line?
- [ ] Are data structures left in a valid state on panic?
- [ ] Do you need a panic guard for cleanup?
- [ ] Could a destructor see invalid state?
## 5. Documentation
- [ ] Have you written a `// SAFETY:` comment explaining:
- What invariants must hold?
- Why those invariants are upheld here?
- [ ] For `unsafe fn`, have you written `# Safety` docs explaining:
- What the caller must guarantee?
- What happens if requirements are violated?
## 6. Testing and Verification
- [ ] Can you add debug assertions to verify invariants?
- [ ] Have you tested with Miri (`cargo miri test`)?
- [ ] Have you tested with address sanitizer (`RUSTFLAGS="-Zsanitizer=address"`)?
- [ ] Have you considered fuzzing the unsafe code?
## Quick Reference: Common SAFETY Comments
```rust
// SAFETY: We checked that index < len above, so this is in bounds.
// SAFETY: The pointer was created from a valid reference and hasn't been invalidated.
// SAFETY: We hold the lock, guaranteeing exclusive access.
// SAFETY: The type is #[repr(C)] and all fields are initialized.
// SAFETY: Caller guarantees the pointer is non-null and properly aligned.
```
## Decision Flowchart
```
Need unsafe?
|
v
Can you use safe Rust? --Yes--> Don't use unsafe
|
No
v
Can you use existing safe abstraction? --Yes--> Use it (std, crates)
|
No
v
Document all invariants
|
v
Add SAFETY comments
|
v
Write the unsafe code
|
v
Test with Miri
```

View File

@@ -1,253 +0,0 @@
# Common Unsafe Pitfalls and Fixes
A reference of frequently encountered unsafe bugs and how to fix them.
## Pitfall 1: Dangling Pointer from Local
**Bug:**
```rust
fn bad() -> *const i32 {
let x = 42;
&x as *const i32 // Dangling after return!
}
```
**Fix:**
```rust
fn good() -> Box<i32> {
Box::new(42) // Heap allocation lives beyond function
}
// Or return the value itself
fn better() -> i32 {
42
}
```
## Pitfall 2: CString Lifetime
**Bug:**
```rust
fn bad() -> *const c_char {
let s = CString::new("hello").unwrap();
s.as_ptr() // Dangling! CString dropped
}
```
**Fix:**
```rust
fn good(s: &CString) -> *const c_char {
s.as_ptr() // Caller keeps CString alive
}
// Or take ownership
fn also_good(s: CString) -> *const c_char {
s.into_raw() // Caller must free with CString::from_raw
}
```
## Pitfall 3: Vec set_len with Uninitialized Data
**Bug:**
```rust
fn bad() -> Vec<String> {
let mut v = Vec::with_capacity(10);
unsafe { v.set_len(10); } // Strings are uninitialized!
v
}
```
**Fix:**
```rust
fn good() -> Vec<String> {
let mut v = Vec::with_capacity(10);
for _ in 0..10 {
v.push(String::new());
}
v
}
// Or use resize
fn also_good() -> Vec<String> {
let mut v = Vec::new();
v.resize(10, String::new());
v
}
```
## Pitfall 4: Reference to Packed Field
**Bug:**
```rust
#[repr(packed)]
struct Packed { a: u8, b: u32 }
fn bad(p: &Packed) -> &u32 {
&p.b // UB: misaligned reference!
}
```
**Fix:**
```rust
fn good(p: &Packed) -> u32 {
unsafe { std::ptr::addr_of!(p.b).read_unaligned() }
}
```
## Pitfall 5: Mutable Aliasing Through Raw Pointers
**Bug:**
```rust
fn bad() {
let mut x = 42;
let ptr1 = &mut x as *mut i32;
let ptr2 = &mut x as *mut i32; // Already have ptr1!
unsafe {
*ptr1 = 1;
*ptr2 = 2; // Aliasing mutable pointers!
}
}
```
**Fix:**
```rust
fn good() {
let mut x = 42;
let ptr = &mut x as *mut i32;
unsafe {
*ptr = 1;
*ptr = 2; // Same pointer, sequential access
}
}
```
## Pitfall 6: Transmute to Wrong Size
**Bug:**
```rust
fn bad() {
let x: u32 = 42;
let y: u64 = unsafe { std::mem::transmute(x) }; // UB: size mismatch!
}
```
**Fix:**
```rust
fn good() {
let x: u32 = 42;
let y: u64 = x as u64; // Use conversion
}
```
## Pitfall 7: Invalid Enum Discriminant
**Bug:**
```rust
#[repr(u8)]
enum Status { A = 0, B = 1, C = 2 }
fn bad(raw: u8) -> Status {
unsafe { std::mem::transmute(raw) } // UB if raw > 2!
}
```
**Fix:**
```rust
fn good(raw: u8) -> Option<Status> {
match raw {
0 => Some(Status::A),
1 => Some(Status::B),
2 => Some(Status::C),
_ => None,
}
}
```
## Pitfall 8: FFI Panic Unwinding
**Bug:**
```rust
#[no_mangle]
extern "C" fn callback(x: i32) -> i32 {
if x < 0 {
panic!("negative!"); // UB: unwinding across FFI!
}
x * 2
}
```
**Fix:**
```rust
#[no_mangle]
extern "C" fn callback(x: i32) -> i32 {
std::panic::catch_unwind(|| {
if x < 0 {
panic!("negative!");
}
x * 2
}).unwrap_or(-1) // Return error code on panic
}
```
## Pitfall 9: Double Free from Clone + into_raw
**Bug:**
```rust
struct Handle(*mut c_void);
impl Clone for Handle {
fn clone(&self) -> Self {
Handle(self.0) // Both now "own" same pointer!
}
}
impl Drop for Handle {
fn drop(&mut self) {
unsafe { free(self.0); } // Double free when both drop!
}
}
```
**Fix:**
```rust
struct Handle(*mut c_void);
// Don't implement Clone, or implement proper reference counting
impl Handle {
fn clone_ptr(&self) -> *mut c_void {
self.0 // Return raw pointer, no ownership
}
}
```
## Pitfall 10: Forget Doesn't Run Destructors
**Bug:**
```rust
fn bad() {
let guard = lock.lock();
std::mem::forget(guard); // Lock never released!
}
```
**Fix:**
```rust
fn good() {
let guard = lock.lock();
// Let guard drop naturally
// or explicitly: drop(guard);
}
```
## Quick Reference Table
| Pitfall | Detection | Fix |
|---------|-----------|-----|
| Dangling pointer | Miri | Extend lifetime or heap allocate |
| Uninitialized read | Miri | Use MaybeUninit properly |
| Misaligned access | Miri, UBsan | read_unaligned, copy by value |
| Data race | TSan | Use atomics or mutex |
| Double free | ASan | Track ownership carefully |
| Invalid enum | Manual review | Use TryFrom |
| FFI panic | Testing | catch_unwind |
| Type confusion | Miri | Match types exactly |

View File

@@ -1,113 +0,0 @@
# Checklist: Reviewing Unsafe Code
Use this checklist when reviewing code containing `unsafe`.
## 1. Surface-Level Checks
- [ ] Does every `unsafe` block have a `// SAFETY:` comment?
- [ ] Does every `unsafe fn` have `# Safety` documentation?
- [ ] Are the safety comments specific and verifiable, not vague?
- [ ] Is the unsafe code minimized (smallest possible unsafe block)?
## 2. Pointer Validity
For each pointer dereference:
- [ ] **Non-null**: Is null checked before dereference?
- [ ] **Aligned**: Is alignment verified or guaranteed by construction?
- [ ] **Valid**: Does the pointer point to allocated memory?
- [ ] **Initialized**: Is the memory initialized before reading?
- [ ] **Lifetime**: Is the memory valid for the entire use duration?
- [ ] **Unique**: For `&mut`, is there only one mutable reference?
## 3. Memory Safety
- [ ] **No aliasing**: Are `&` and `&mut` never created to the same memory simultaneously?
- [ ] **No use-after-free**: Is memory not accessed after deallocation?
- [ ] **No double-free**: Is memory freed exactly once?
- [ ] **No data races**: Is concurrent access properly synchronized?
- [ ] **Bounds checked**: Are array/slice accesses in bounds?
## 4. Type Safety
- [ ] **Transmute**: Are transmuted types actually compatible?
- [ ] **Repr**: Do FFI types have `#[repr(C)]`?
- [ ] **Enum values**: Are enum discriminants validated from external sources?
- [ ] **Unions**: Is the correct union field accessed?
## 5. Panic Safety
- [ ] What state is the program in if this code panics?
- [ ] Are partially constructed objects properly cleaned up?
- [ ] Do Drop implementations see valid state?
- [ ] Is there a panic guard if needed?
## 6. FFI-Specific Checks
- [ ] **Types**: Do Rust types match C types exactly?
- [ ] **Strings**: Are strings properly null-terminated?
- [ ] **Ownership**: Is it clear who owns/frees memory?
- [ ] **Thread safety**: Are callbacks thread-safe?
- [ ] **Panic boundary**: Are panics caught before crossing FFI?
- [ ] **Error handling**: Are C-style errors properly handled?
## 7. Concurrency Checks
- [ ] **Send/Sync**: Are manual implementations actually sound?
- [ ] **Atomics**: Are memory orderings correct?
- [ ] **Locks**: Is there potential for deadlock?
- [ ] **Data races**: Is all shared mutable state synchronized?
## 8. Red Flags (Require Extra Scrutiny)
| Pattern | Concern |
|---------|---------|
| `transmute` | Type compatibility, provenance |
| `as` on pointers | Alignment, type punning |
| `static mut` | Data races |
| `*const T as *mut T` | Aliasing violation |
| Manual `Send`/`Sync` | Thread safety |
| `assume_init` | Initialization |
| `set_len` on Vec | Uninitialized memory |
| `from_raw_parts` | Lifetime, validity |
| `offset`/`add`/`sub` | Out of bounds |
| FFI callbacks | Panic safety |
## 9. Verification Questions
Ask the author:
- "What would happen if [X invariant] was violated?"
- "How do you know [pointer/reference] is valid here?"
- "What if this panics at [specific line]?"
- "Who is responsible for freeing this memory?"
## 10. Testing Requirements
- [ ] Has this been tested with Miri?
- [ ] Are there unit tests covering edge cases?
- [ ] Are there tests for error conditions?
- [ ] Has concurrent code been tested under stress?
## Review Severity Guide
| Severity | Requires |
|----------|----------|
| `transmute` | Two reviewers, Miri test |
| Manual `Send`/`Sync` | Thread safety expert review |
| FFI | Documentation of C interface |
| `static mut` | Justification for not using atomic/mutex |
| Pointer arithmetic | Bounds proof |
## Sample Review Comments
```
// Good SAFETY comment ✓
// SAFETY: index was checked to be < len on line 42
// Needs improvement ✗
// SAFETY: This is safe because we know it works
// Missing information ✗
// SAFETY: ptr is valid
// (Why is it valid? How do we know?)
```

View File

@@ -1,353 +0,0 @@
# FFI Best Practices and Patterns
Examples of safe and idiomatic Rust-C interoperability.
## Pattern 1: Basic FFI Wrapper
```rust
use std::ffi::{CStr, CString};
use std::os::raw::{c_char, c_int, c_void};
use std::ptr::NonNull;
// Raw C API
mod ffi {
use super::*;
extern "C" {
pub fn lib_create(name: *const c_char) -> *mut c_void;
pub fn lib_destroy(handle: *mut c_void);
pub fn lib_process(handle: *mut c_void, data: *const u8, len: usize) -> c_int;
pub fn lib_get_error() -> *const c_char;
}
}
// Safe Rust wrapper
pub struct Library {
handle: NonNull<c_void>,
}
#[derive(Debug)]
pub struct LibraryError(String);
impl Library {
pub fn new(name: &str) -> Result<Self, LibraryError> {
let c_name = CString::new(name).map_err(|_| LibraryError("invalid name".into()))?;
let handle = unsafe { ffi::lib_create(c_name.as_ptr()) };
NonNull::new(handle)
.map(|handle| Self { handle })
.ok_or_else(|| Self::last_error())
}
pub fn process(&self, data: &[u8]) -> Result<(), LibraryError> {
let result = unsafe {
ffi::lib_process(self.handle.as_ptr(), data.as_ptr(), data.len())
};
if result == 0 {
Ok(())
} else {
Err(Self::last_error())
}
}
fn last_error() -> LibraryError {
let ptr = unsafe { ffi::lib_get_error() };
if ptr.is_null() {
LibraryError("unknown error".into())
} else {
let msg = unsafe { CStr::from_ptr(ptr) }
.to_string_lossy()
.into_owned();
LibraryError(msg)
}
}
}
impl Drop for Library {
fn drop(&mut self) {
unsafe { ffi::lib_destroy(self.handle.as_ptr()); }
}
}
// Prevent accidental copies
impl !Clone for Library {}
```
## Pattern 2: Callback Registration
```rust
use std::os::raw::{c_int, c_void};
use std::panic::{catch_unwind, AssertUnwindSafe};
type CCallback = extern "C" fn(value: c_int, user_data: *mut c_void) -> c_int;
extern "C" {
fn register_callback(cb: CCallback, user_data: *mut c_void);
fn unregister_callback();
}
/// Safely register a Rust closure as a C callback.
pub struct CallbackGuard<F> {
_closure: Box<F>,
}
impl<F: FnMut(i32) -> i32 + 'static> CallbackGuard<F> {
pub fn register(closure: F) -> Self {
let boxed = Box::new(closure);
let user_data = Box::into_raw(boxed) as *mut c_void;
extern "C" fn trampoline<F: FnMut(i32) -> i32>(
value: c_int,
user_data: *mut c_void,
) -> c_int {
let result = catch_unwind(AssertUnwindSafe(|| {
let closure = unsafe { &mut *(user_data as *mut F) };
closure(value as i32) as c_int
}));
result.unwrap_or(-1)
}
unsafe {
register_callback(trampoline::<F>, user_data);
}
Self {
// SAFETY: We just created this box and need to keep it alive
_closure: unsafe { Box::from_raw(user_data as *mut F) },
}
}
}
impl<F> Drop for CallbackGuard<F> {
fn drop(&mut self) {
unsafe { unregister_callback(); }
// Box in _closure is dropped automatically
}
}
// Usage
fn example() {
let multiplier = 2;
let _guard = CallbackGuard::register(move |x| x * multiplier);
// Callback is active until _guard is dropped
}
```
## Pattern 3: Opaque Handle Types
```rust
use std::marker::PhantomData;
// Opaque type markers - prevents mixing up handles
#[repr(C)]
pub struct DatabaseHandle {
_data: [u8; 0],
_marker: PhantomData<(*mut u8, std::marker::PhantomPinned)>,
}
#[repr(C)]
pub struct ConnectionHandle {
_data: [u8; 0],
_marker: PhantomData<(*mut u8, std::marker::PhantomPinned)>,
}
mod ffi {
use super::*;
extern "C" {
pub fn db_open(path: *const c_char) -> *mut DatabaseHandle;
pub fn db_close(db: *mut DatabaseHandle);
pub fn db_connect(db: *mut DatabaseHandle) -> *mut ConnectionHandle;
pub fn conn_close(conn: *mut ConnectionHandle);
pub fn conn_query(conn: *mut ConnectionHandle, sql: *const c_char) -> c_int;
}
}
// Type-safe wrappers
pub struct Database {
handle: NonNull<DatabaseHandle>,
}
pub struct Connection<'db> {
handle: NonNull<ConnectionHandle>,
_db: PhantomData<&'db Database>,
}
impl Database {
pub fn open(path: &str) -> Result<Self, ()> {
let c_path = CString::new(path).map_err(|_| ())?;
let handle = unsafe { ffi::db_open(c_path.as_ptr()) };
NonNull::new(handle).map(|h| Self { handle: h }).ok_or(())
}
pub fn connect(&self) -> Result<Connection<'_>, ()> {
let handle = unsafe { ffi::db_connect(self.handle.as_ptr()) };
NonNull::new(handle)
.map(|h| Connection { handle: h, _db: PhantomData })
.ok_or(())
}
}
impl Drop for Database {
fn drop(&mut self) {
// All Connections must be dropped first (enforced by lifetime)
unsafe { ffi::db_close(self.handle.as_ptr()); }
}
}
impl Connection<'_> {
pub fn query(&self, sql: &str) -> Result<(), ()> {
let c_sql = CString::new(sql).map_err(|_| ())?;
let result = unsafe { ffi::conn_query(self.handle.as_ptr(), c_sql.as_ptr()) };
if result == 0 { Ok(()) } else { Err(()) }
}
}
impl Drop for Connection<'_> {
fn drop(&mut self) {
unsafe { ffi::conn_close(self.handle.as_ptr()); }
}
}
```
## Pattern 4: Error Handling Across FFI
```rust
use std::os::raw::c_int;
// Error codes for C
pub const SUCCESS: c_int = 0;
pub const ERR_NULL_PTR: c_int = 1;
pub const ERR_INVALID_UTF8: c_int = 2;
pub const ERR_IO: c_int = 3;
pub const ERR_PANIC: c_int = -1;
// Thread-local error storage
thread_local! {
static LAST_ERROR: std::cell::RefCell<Option<Box<dyn std::error::Error>>> =
std::cell::RefCell::new(None);
}
fn set_last_error<E: std::error::Error + 'static>(err: E) {
LAST_ERROR.with(|e| {
*e.borrow_mut() = Some(Box::new(err));
});
}
/// Get the last error message. Caller must free with `free_string`.
#[no_mangle]
pub extern "C" fn get_last_error() -> *mut c_char {
LAST_ERROR.with(|e| {
e.borrow()
.as_ref()
.map(|err| {
CString::new(err.to_string())
.unwrap_or_else(|_| CString::new("error").unwrap())
.into_raw()
})
.unwrap_or(std::ptr::null_mut())
})
}
/// Free a string returned by this library.
#[no_mangle]
pub extern "C" fn free_string(s: *mut c_char) {
if !s.is_null() {
// SAFETY: String was created by CString::into_raw
unsafe { drop(CString::from_raw(s)); }
}
}
/// Example function with proper error handling.
#[no_mangle]
pub extern "C" fn do_operation(data: *const u8, len: usize) -> c_int {
let result = catch_unwind(AssertUnwindSafe(|| -> Result<(), c_int> {
if data.is_null() {
return Err(ERR_NULL_PTR);
}
let slice = unsafe { std::slice::from_raw_parts(data, len) };
std::str::from_utf8(slice)
.map_err(|e| {
set_last_error(e);
ERR_INVALID_UTF8
})?;
// Do actual work...
Ok(())
}));
match result {
Ok(Ok(())) => SUCCESS,
Ok(Err(code)) => code,
Err(_) => ERR_PANIC,
}
}
```
## Pattern 5: Struct with C Layout
```rust
use std::os::raw::{c_char, c_int};
/// A C-compatible configuration struct.
#[repr(C)]
pub struct Config {
pub version: c_int,
pub flags: u32,
pub name: [c_char; 64],
pub name_len: usize,
}
impl Config {
pub fn new(version: i32, flags: u32, name: &str) -> Option<Self> {
if name.len() >= 64 {
return None;
}
let mut config = Self {
version: version as c_int,
flags,
name: [0; 64],
name_len: name.len(),
};
// Copy name bytes
for (i, byte) in name.bytes().enumerate() {
config.name[i] = byte as c_char;
}
Some(config)
}
pub fn name(&self) -> &str {
let bytes = unsafe {
std::slice::from_raw_parts(
self.name.as_ptr() as *const u8,
self.name_len,
)
};
// SAFETY: We only store valid UTF-8 in new()
unsafe { std::str::from_utf8_unchecked(bytes) }
}
}
// Verify layout at compile time
const _: () = {
assert!(std::mem::size_of::<Config>() == 80); // 4 + 4 + 64 + 8
assert!(std::mem::align_of::<Config>() == 8);
};
```
## Key FFI Guidelines
1. **Always use `#[repr(C)]`** for types crossing FFI
2. **Handle null pointers** at the boundary
3. **Catch panics** before returning to C
4. **Document ownership** clearly
5. **Use opaque types** for type safety
6. **Keep unsafe minimal** and well-documented

View File

@@ -1,272 +0,0 @@
# Safe Abstraction Examples
Examples of building safe APIs on top of unsafe code.
## Example 1: Simple Wrapper with Bounds Check
```rust
/// A slice wrapper that provides unchecked access internally
/// but safe access externally.
pub struct SafeSlice<'a, T> {
ptr: *const T,
len: usize,
_marker: std::marker::PhantomData<&'a T>,
}
impl<'a, T> SafeSlice<'a, T> {
/// Creates a SafeSlice from a regular slice.
pub fn new(slice: &'a [T]) -> Self {
Self {
ptr: slice.as_ptr(),
len: slice.len(),
_marker: std::marker::PhantomData,
}
}
/// Safe get - returns Option.
pub fn get(&self, index: usize) -> Option<&T> {
if index < self.len {
// SAFETY: We just verified index < len
Some(unsafe { &*self.ptr.add(index) })
} else {
None
}
}
/// Unsafe get - caller must ensure bounds.
///
/// # Safety
/// `index` must be less than `self.len()`.
pub unsafe fn get_unchecked(&self, index: usize) -> &T {
debug_assert!(index < self.len);
&*self.ptr.add(index)
}
pub fn len(&self) -> usize {
self.len
}
}
```
## Example 2: Resource Wrapper with Drop
```rust
use std::ptr::NonNull;
/// Safe wrapper around a C-allocated buffer.
pub struct CBuffer {
ptr: NonNull<u8>,
len: usize,
}
extern "C" {
fn c_alloc(size: usize) -> *mut u8;
fn c_free(ptr: *mut u8);
}
impl CBuffer {
/// Creates a new buffer. Returns None if allocation fails.
pub fn new(size: usize) -> Option<Self> {
let ptr = unsafe { c_alloc(size) };
NonNull::new(ptr).map(|ptr| Self { ptr, len: size })
}
/// Returns a slice view of the buffer.
pub fn as_slice(&self) -> &[u8] {
// SAFETY: ptr is valid for len bytes (from c_alloc contract)
unsafe { std::slice::from_raw_parts(self.ptr.as_ptr(), self.len) }
}
/// Returns a mutable slice view.
pub fn as_mut_slice(&mut self) -> &mut [u8] {
// SAFETY: We have &mut self, so exclusive access
unsafe { std::slice::from_raw_parts_mut(self.ptr.as_ptr(), self.len) }
}
}
impl Drop for CBuffer {
fn drop(&mut self) {
// SAFETY: ptr was allocated by c_alloc and not yet freed
unsafe { c_free(self.ptr.as_ptr()); }
}
}
// Prevent double-free
impl !Clone for CBuffer {}
// Safe to send between threads (assuming c_alloc is thread-safe)
unsafe impl Send for CBuffer {}
```
## Example 3: Interior Mutability with UnsafeCell
```rust
use std::cell::UnsafeCell;
use std::sync::atomic::{AtomicBool, Ordering};
/// A simple spinlock demonstrating safe abstraction over UnsafeCell.
pub struct SpinLock<T> {
locked: AtomicBool,
data: UnsafeCell<T>,
}
pub struct SpinLockGuard<'a, T> {
lock: &'a SpinLock<T>,
}
impl<T> SpinLock<T> {
pub const fn new(data: T) -> Self {
Self {
locked: AtomicBool::new(false),
data: UnsafeCell::new(data),
}
}
pub fn lock(&self) -> SpinLockGuard<'_, T> {
// Spin until we acquire the lock
while self.locked.compare_exchange_weak(
false,
true,
Ordering::Acquire,
Ordering::Relaxed,
).is_err() {
std::hint::spin_loop();
}
SpinLockGuard { lock: self }
}
}
impl<T> std::ops::Deref for SpinLockGuard<'_, T> {
type Target = T;
fn deref(&self) -> &T {
// SAFETY: We hold the lock, so we have exclusive access
unsafe { &*self.lock.data.get() }
}
}
impl<T> std::ops::DerefMut for SpinLockGuard<'_, T> {
fn deref_mut(&mut self) -> &mut T {
// SAFETY: We hold the lock, so we have exclusive access
unsafe { &mut *self.lock.data.get() }
}
}
impl<T> Drop for SpinLockGuard<'_, T> {
fn drop(&mut self) {
self.lock.locked.store(false, Ordering::Release);
}
}
// SAFETY: The lock ensures only one thread accesses data at a time
unsafe impl<T: Send> Sync for SpinLock<T> {}
unsafe impl<T: Send> Send for SpinLock<T> {}
```
## Example 4: Iterator with Lifetime Tracking
```rust
use std::marker::PhantomData;
/// An iterator over raw pointer range with proper lifetime tracking.
pub struct PtrIter<'a, T> {
current: *const T,
end: *const T,
_marker: PhantomData<&'a T>,
}
impl<'a, T> PtrIter<'a, T> {
/// Creates an iterator from a slice.
pub fn new(slice: &'a [T]) -> Self {
let ptr = slice.as_ptr();
Self {
current: ptr,
// SAFETY: Adding len to slice pointer is always valid
end: unsafe { ptr.add(slice.len()) },
_marker: PhantomData,
}
}
}
impl<'a, T> Iterator for PtrIter<'a, T> {
type Item = &'a T;
fn next(&mut self) -> Option<Self::Item> {
if self.current == self.end {
None
} else {
// SAFETY:
// - current < end (checked above)
// - PhantomData<&'a T> ensures the data lives for 'a
let item = unsafe { &*self.current };
self.current = unsafe { self.current.add(1) };
Some(item)
}
}
}
```
## Example 5: Builder Pattern with Delayed Initialization
```rust
use std::mem::MaybeUninit;
/// A builder that collects exactly N items, then produces an array.
pub struct ArrayBuilder<T, const N: usize> {
data: [MaybeUninit<T>; N],
count: usize,
}
impl<T, const N: usize> ArrayBuilder<T, N> {
pub fn new() -> Self {
Self {
// SAFETY: MaybeUninit doesn't require initialization
data: unsafe { MaybeUninit::uninit().assume_init() },
count: 0,
}
}
pub fn push(&mut self, value: T) -> Result<(), T> {
if self.count >= N {
return Err(value);
}
self.data[self.count].write(value);
self.count += 1;
Ok(())
}
pub fn build(self) -> Option<[T; N]> {
if self.count != N {
return None;
}
// SAFETY: All N elements have been initialized
let result = unsafe {
// Prevent drop of self.data (we're moving out)
let data = std::ptr::read(&self.data);
std::mem::forget(self);
// Transmute MaybeUninit array to initialized array
std::mem::transmute_copy::<[MaybeUninit<T>; N], [T; N]>(&data)
};
Some(result)
}
}
impl<T, const N: usize> Drop for ArrayBuilder<T, N> {
fn drop(&mut self) {
// Drop only initialized elements
for i in 0..self.count {
// SAFETY: Elements 0..count are initialized
unsafe { self.data[i].assume_init_drop(); }
}
}
}
```
## Key Patterns
1. **Encapsulation**: Hide unsafe behind safe public API
2. **Invariant maintenance**: Use private fields to maintain invariants
3. **PhantomData**: Track lifetimes and ownership for pointers
4. **RAII**: Use Drop for cleanup
5. **Type state**: Use types to encode valid states

View File

@@ -1,17 +0,0 @@
{
"name": "unsafe-checker",
"version": "1.0.0",
"description": "Unsafe Rust code review and safety abstraction checker",
"source": "https://github.com/Rust-Coding-Guidelines/rust-coding-guidelines-zh",
"lastUpdated": "2026-01-16",
"ruleCount": 47,
"sections": [
{ "id": "general", "name": "General Principles", "count": 3 },
{ "id": "safety", "name": "Safety Abstraction", "count": 11 },
{ "id": "ptr", "name": "Raw Pointers", "count": 6 },
{ "id": "union", "name": "Union", "count": 2 },
{ "id": "mem", "name": "Memory Layout", "count": 6 },
{ "id": "ffi", "name": "FFI", "count": 18 },
{ "id": "io", "name": "I/O Safety", "count": 1 }
]
}

View File

@@ -1,77 +0,0 @@
# Unsafe Checker - Section Definitions
## Section Overview
| # | Section | Prefix | Level | Count | Impact |
|---|---------|--------|-------|-------|--------|
| 1 | General Principles | `general-` | CRITICAL | 3 | Foundational unsafe usage guidance |
| 2 | Safety Abstraction | `safety-` | CRITICAL | 11 | Building sound safe APIs |
| 3 | Raw Pointers | `ptr-` | HIGH | 6 | Pointer manipulation safety |
| 4 | Union | `union-` | HIGH | 2 | Union type safety |
| 5 | Memory Layout | `mem-` | HIGH | 6 | Data representation correctness |
| 6 | FFI | `ffi-` | CRITICAL | 18 | C interoperability safety |
| 7 | I/O Safety | `io-` | MEDIUM | 1 | Handle/resource safety |
## Section Details
### 1. General Principles (`general-`)
**Focus**: When and why to use unsafe
- P.UNS.01: Don't abuse unsafe to escape borrow checker
- P.UNS.02: Don't use unsafe blindly for performance
- G.UNS.01: Don't create aliases for "unsafe" named items
### 2. Safety Abstraction (`safety-`)
**Focus**: Building sound safe abstractions over unsafe code
Key invariants:
- Panic safety
- Memory initialization
- Send/Sync correctness
- API soundness
### 3. Raw Pointers (`ptr-`)
**Focus**: Safe pointer manipulation patterns
- Aliasing rules
- Alignment requirements
- Null/dangling prevention
- Type casting
### 4. Union (`union-`)
**Focus**: Safe union usage (primarily for C interop)
- Initialization rules
- Lifetime considerations
- Type punning dangers
### 5. Memory Layout (`mem-`)
**Focus**: Correct data representation
- `#[repr(C)]` usage
- Alignment and padding
- Uninitialized memory
- Cross-process memory
### 6. FFI (`ffi-`)
**Focus**: Safe C interoperability
Subcategories:
- String handling (CString, CStr)
- Type compatibility
- Error handling across FFI
- Thread safety
- Resource management
### 7. I/O Safety (`io-`)
**Focus**: Handle and resource ownership
- Raw file descriptor safety
- Handle validity guarantees

View File

@@ -1,53 +0,0 @@
# Rule Template
Use this template for all unsafe-checker rules.
---
```markdown
---
id: {prefix}-{number}
original_id: P.UNS.XXX.YY or G.UNS.XXX.YY
level: P|G
impact: CRITICAL|HIGH|MEDIUM
clippy: <clippy_lint_name> (if applicable)
---
# {Rule Title}
## Summary
One-sentence description of what this rule requires.
## Rationale
Why this rule matters for safety/soundness.
## Bad Example
```rust
// DON'T: Description of the anti-pattern
<code that violates the rule>
```
## Good Example
```rust
// DO: Description of the correct pattern
<code that follows the rule>
```
## Common Violations
1. Violation pattern 1
2. Violation pattern 2
## Checklist
- [ ] Check item 1
- [ ] Check item 2
## Related Rules
- `{other-rule-id}`: Brief description
```

View File

@@ -1,122 +0,0 @@
---
id: ffi-01
original_id: P.UNS.FFI.01
level: P
impact: HIGH
---
# Avoid Passing Strings Directly to C from Public Rust API
## Summary
Use `CString` and `CStr` for string handling at FFI boundaries. Never pass Rust `String` or `&str` directly to C.
## Rationale
- Rust strings are UTF-8, not null-terminated
- C strings require null terminator
- Rust strings may contain interior null bytes
- Memory layout differs between Rust String and C char*
## Bad Example
```rust
extern "C" {
fn c_print(s: *const u8);
fn c_strlen(s: *const u8) -> usize;
}
// DON'T: Pass Rust string directly
fn bad_print(s: &str) {
unsafe {
c_print(s.as_ptr()); // Not null-terminated!
}
}
// DON'T: Assume length matches
fn bad_strlen(s: &str) -> usize {
unsafe {
c_strlen(s.as_ptr()) // May read past buffer
}
}
// DON'T: Use String in FFI signatures
extern "C" fn bad_callback(s: String) { // Wrong!
println!("{}", s);
}
```
## Good Example
```rust
use std::ffi::{CString, CStr};
use std::os::raw::c_char;
extern "C" {
fn c_print(s: *const c_char);
fn c_strlen(s: *const c_char) -> usize;
fn c_get_string() -> *const c_char;
}
// DO: Convert to CString for passing to C
fn good_print(s: &str) -> Result<(), std::ffi::NulError> {
let c_string = CString::new(s)?; // Adds null terminator, checks for interior nulls
unsafe {
c_print(c_string.as_ptr());
}
Ok(())
}
// DO: Use CStr for receiving C strings
fn good_receive() -> String {
unsafe {
let ptr = c_get_string();
let c_str = CStr::from_ptr(ptr);
c_str.to_string_lossy().into_owned()
}
}
// DO: Handle interior null bytes
fn handle_nulls(s: &str) {
match CString::new(s) {
Ok(c_string) => unsafe { c_print(c_string.as_ptr()) },
Err(e) => {
// String contains interior null at position e.nul_position()
eprintln!("String contains null byte at {}", e.nul_position());
}
}
}
// DO: Use proper types in callbacks
extern "C" fn good_callback(s: *const c_char) {
if !s.is_null() {
let c_str = unsafe { CStr::from_ptr(s) };
if let Ok(rust_str) = c_str.to_str() {
println!("{}", rust_str);
}
}
}
```
## String Type Comparison
| Type | Null-terminated | Encoding | Use |
|------|-----------------|----------|-----|
| `String` | No | UTF-8 | Rust owned |
| `&str` | No | UTF-8 | Rust borrowed |
| `CString` | Yes | Byte | Rust-to-C owned |
| `&CStr` | Yes | Byte | Rust-to-C borrowed |
| `*const c_char` | Yes | Byte | FFI pointer |
| `OsString` | Platform | Platform | Paths, env |
## Checklist
- [ ] Am I passing Rust strings to C? → Use CString
- [ ] Am I receiving C strings? → Use CStr
- [ ] Does my string contain null bytes? → Handle NulError
- [ ] Am I checking for null pointers from C?
## Related Rules
- `ffi-02`: Read documentation for std::ffi types
- `ffi-06`: Ensure C-ABI string compatibility

View File

@@ -1,133 +0,0 @@
---
id: ffi-02
original_id: P.UNS.FFI.02
level: P
impact: MEDIUM
---
# Read Documentation Carefully When Using std::ffi Types
## Summary
The `std::ffi` module has many types with subtle differences. Read their documentation carefully to avoid misuse.
## Key Types in std::ffi
### CString vs CStr
```rust
use std::ffi::{CString, CStr};
use std::os::raw::c_char;
// CString: Owned, heap-allocated, null-terminated
// - Use when creating strings to pass to C
// - Owns the memory
let owned = CString::new("hello").unwrap();
let ptr: *const c_char = owned.as_ptr();
// ptr valid until `owned` is dropped
// CStr: Borrowed, null-terminated
// - Use when receiving strings from C
// - Does not own memory
let borrowed: &CStr = unsafe { CStr::from_ptr(ptr) };
// borrowed valid as long as ptr is valid
```
### OsString vs OsStr
```rust
use std::ffi::{OsString, OsStr};
use std::path::Path;
// OsString/OsStr: Platform-native strings
// - Windows: potentially ill-formed UTF-16
// - Unix: arbitrary bytes
// - Use for paths and environment variables
let path = Path::new("/some/path");
let os_str: &OsStr = path.as_os_str();
// Convert to Rust string (may fail)
if let Some(s) = os_str.to_str() {
println!("Valid UTF-8: {}", s);
}
```
### c_void and Opaque Types
```rust
use std::ffi::c_void;
extern "C" {
fn get_handle() -> *mut c_void;
fn use_handle(h: *mut c_void);
}
// c_void is for truly opaque pointers
// Better: use dedicated opaque types (see ffi-17)
```
## Common Pitfalls
```rust
use std::ffi::CString;
// PITFALL 1: CString::as_ptr() lifetime
fn bad_ptr() -> *const i8 {
let s = CString::new("hello").unwrap();
s.as_ptr() // Dangling! s dropped at end of function
}
fn good_ptr(s: &CString) -> *const i8 {
s.as_ptr() // OK: s outlives the pointer
}
// PITFALL 2: CString::new with interior nulls
let result = CString::new("hello\0world");
assert!(result.is_err()); // Interior null!
// PITFALL 3: CStr::from_ptr safety
unsafe {
let ptr: *const i8 = std::ptr::null();
// let cstr = CStr::from_ptr(ptr); // UB: null pointer!
// Always check for null first
if !ptr.is_null() {
let cstr = CStr::from_ptr(ptr);
}
}
// PITFALL 4: CStr assumes valid null-terminated string
unsafe {
let bytes = [104, 101, 108, 108, 111]; // "hello" without null
let ptr = bytes.as_ptr() as *const i8;
// let cstr = CStr::from_ptr(ptr); // UB: no null terminator!
// Use from_bytes_with_nul instead
let bytes_with_nul = b"hello\0";
let cstr = CStr::from_bytes_with_nul(bytes_with_nul).unwrap();
}
```
## Type Selection Guide
| Scenario | Type |
|----------|------|
| Create string for C | `CString` |
| Borrow string from C | `&CStr` |
| File paths | `OsString`, `Path` |
| Environment variables | `OsString` |
| Opaque C pointers | Newtype over `*mut c_void` |
| C integers | `c_int`, `c_long`, etc. |
## Checklist
- [ ] Have I read the docs for the std::ffi type I'm using?
- [ ] Am I aware of the lifetime constraints?
- [ ] Am I handling potential errors (NulError, UTF-8 errors)?
- [ ] Is there a better type for my use case?
## Related Rules
- `ffi-01`: Use CString/CStr for strings
- `ffi-17`: Use opaque types instead of c_void

View File

@@ -1,162 +0,0 @@
---
id: ffi-03
original_id: P.UNS.FFI.03
level: P
impact: CRITICAL
---
# Implement Drop for Rust Types Wrapping Memory-Managing C Pointers
## Summary
When wrapping a C pointer that owns memory, implement `Drop` to call the appropriate C deallocation function.
## Rationale
- C allocated memory must be freed with the matching C function
- Rust's default drop won't clean up foreign memory
- Resource leaks and double-frees are common FFI bugs
## Bad Example
```rust
extern "C" {
fn create_resource() -> *mut Resource;
fn free_resource(r: *mut Resource);
}
// DON'T: Wrapper without Drop
struct ResourceHandle {
ptr: *mut Resource,
}
impl ResourceHandle {
fn new() -> Self {
Self {
ptr: unsafe { create_resource() }
}
}
// Memory leak! ptr is never freed
}
// DON'T: Forget to handle null
impl Drop for BadHandle {
fn drop(&mut self) {
unsafe {
free_resource(self.ptr); // Crash if ptr is null!
}
}
}
```
## Good Example
```rust
use std::ptr::NonNull;
extern "C" {
fn create_resource() -> *mut Resource;
fn free_resource(r: *mut Resource);
}
// DO: Proper wrapper with Drop
struct ResourceHandle {
ptr: NonNull<Resource>,
}
impl ResourceHandle {
fn new() -> Option<Self> {
let ptr = unsafe { create_resource() };
NonNull::new(ptr).map(|ptr| Self { ptr })
}
fn as_ptr(&self) -> *mut Resource {
self.ptr.as_ptr()
}
}
impl Drop for ResourceHandle {
fn drop(&mut self) {
// SAFETY: ptr was allocated by create_resource
// and hasn't been freed yet
unsafe {
free_resource(self.ptr.as_ptr());
}
}
}
// Prevent accidental copies that would cause double-free
impl !Clone for ResourceHandle {}
// DO: Document ownership transfer
impl ResourceHandle {
/// Consumes the handle and returns the raw pointer.
///
/// The caller is responsible for freeing the resource.
fn into_raw(self) -> *mut Resource {
let ptr = self.ptr.as_ptr();
std::mem::forget(self); // Don't run Drop
ptr
}
/// Creates a handle from a raw pointer.
///
/// # Safety
///
/// ptr must have been allocated by create_resource()
/// and not yet freed.
unsafe fn from_raw(ptr: *mut Resource) -> Option<Self> {
NonNull::new(ptr).map(|ptr| Self { ptr })
}
}
```
## Complete Pattern with Multiple Resources
```rust
struct Connection {
handle: NonNull<c_void>,
}
struct Statement<'conn> {
handle: NonNull<c_void>,
_conn: std::marker::PhantomData<&'conn Connection>,
}
impl Connection {
fn prepare(&self, sql: &str) -> Option<Statement<'_>> {
let handle = unsafe { db_prepare(self.handle.as_ptr(), sql.as_ptr()) };
NonNull::new(handle).map(|handle| Statement {
handle,
_conn: std::marker::PhantomData,
})
}
}
impl Drop for Connection {
fn drop(&mut self) {
// Statements must be dropped before Connection
// PhantomData ensures this at compile time
unsafe { db_close(self.handle.as_ptr()); }
}
}
impl Drop for Statement<'_> {
fn drop(&mut self) {
unsafe { db_finalize(self.handle.as_ptr()); }
}
}
```
## Checklist
- [ ] Does my wrapper own the C resource?
- [ ] Did I implement Drop with the correct C free function?
- [ ] Did I handle null pointers?
- [ ] Did I prevent Clone/Copy to avoid double-free?
- [ ] Did I consider ownership transfer methods (into_raw/from_raw)?
## Related Rules
- `mem-03`: Don't let String/Vec drop foreign memory
- `ffi-07`: Don't implement Drop for types passed to external code

View File

@@ -1,145 +0,0 @@
---
id: ffi-04
original_id: P.UNS.FFI.04
level: P
impact: CRITICAL
clippy: panic_in_result_fn
---
# Handle Panics When Crossing FFI Boundaries
## Summary
Panics must not unwind across FFI boundaries. Use `catch_unwind` or mark functions as `extern "C-unwind"`.
## Rationale
- Unwinding across C code is undefined behavior
- C has no concept of Rust panics
- Can corrupt C stack frames and cause crashes
- Even with `panic=abort`, still UB to attempt unwinding in `extern "C"`
## Bad Example
```rust
// DON'T: Allow panics to escape to C
#[no_mangle]
pub extern "C" fn callback(data: *const u8, len: usize) -> i32 {
let slice = unsafe { std::slice::from_raw_parts(data, len) };
// If this panics, UB occurs!
let sum: i32 = slice.iter().map(|&x| x as i32).sum();
// If this panics due to overflow in debug, UB!
process(sum)
}
// DON'T: Unwrap in extern functions
#[no_mangle]
pub extern "C" fn parse_config(path: *const c_char) -> i32 {
let path = unsafe { CStr::from_ptr(path) };
let config = std::fs::read_to_string(path.to_str().unwrap()).unwrap(); // Can panic!
0
}
```
## Good Example
```rust
use std::panic::{catch_unwind, AssertUnwindSafe};
use std::ffi::CStr;
use std::os::raw::{c_char, c_int};
// DO: Catch panics at FFI boundary
#[no_mangle]
pub extern "C" fn safe_callback(data: *const u8, len: usize) -> c_int {
let result = catch_unwind(AssertUnwindSafe(|| {
if data.is_null() || len == 0 {
return -1;
}
let slice = unsafe { std::slice::from_raw_parts(data, len) };
let sum: i32 = slice.iter().map(|&x| x as i32).sum();
sum
}));
match result {
Ok(value) => value,
Err(_) => {
// Log error, return error code
eprintln!("Panic caught at FFI boundary");
-1
}
}
}
// DO: Use Result-based API internally
#[no_mangle]
pub extern "C" fn parse_config(path: *const c_char) -> c_int {
let result = catch_unwind(AssertUnwindSafe(|| -> Result<(), Box<dyn std::error::Error>> {
let path = unsafe { CStr::from_ptr(path) }.to_str()?;
let _config = std::fs::read_to_string(path)?;
Ok(())
}));
match result {
Ok(Ok(())) => 0,
Ok(Err(e)) => {
eprintln!("Error: {}", e);
-1
}
Err(_) => {
eprintln!("Panic in parse_config");
-2
}
}
}
// DO: For Rust-calling-Rust across C, use "C-unwind"
#[no_mangle]
pub extern "C-unwind" fn rust_callback_can_unwind() {
// This is OK to panic if called from Rust through C
// The "C-unwind" ABI allows unwinding
panic!("This is allowed");
}
```
## FFI Error Handling Pattern
```rust
// Define error codes
const SUCCESS: c_int = 0;
const ERR_NULL_PTR: c_int = -1;
const ERR_INVALID_UTF8: c_int = -2;
const ERR_IO: c_int = -3;
const ERR_PANIC: c_int = -99;
// Thread-local for detailed error
thread_local! {
static LAST_ERROR: std::cell::RefCell<Option<String>> = std::cell::RefCell::new(None);
}
fn set_error(msg: String) {
LAST_ERROR.with(|e| *e.borrow_mut() = Some(msg));
}
#[no_mangle]
pub extern "C" fn get_last_error() -> *const c_char {
LAST_ERROR.with(|e| {
e.borrow().as_ref().map(|s| s.as_ptr() as *const c_char)
.unwrap_or(std::ptr::null())
})
}
```
## Checklist
- [ ] Does my extern "C" function use catch_unwind?
- [ ] Am I avoiding unwrap/expect in FFI functions?
- [ ] Do I return error codes for error conditions?
- [ ] Have I considered using "C-unwind" for Rust-to-Rust through C?
## Related Rules
- `ffi-08`: Handle errors properly in FFI
- `safety-01`: Panic safety

View File

@@ -1,113 +0,0 @@
---
id: ffi-05
original_id: P.UNS.FFI.05
level: P
impact: HIGH
---
# Use Portable Type Aliases from std or libc
## Summary
Use type aliases from `std::os::raw` or the `libc` crate for C-compatible types. Don't assume sizes of C types.
## Rationale
- C types have platform-dependent sizes (`int` is not always 32 bits)
- `long` is 32 bits on Windows, 64 bits on Unix
- Using Rust primitives directly causes portability bugs
## Bad Example
```rust
// DON'T: Use Rust types directly for C interop
extern "C" {
fn c_function(x: i32, y: i64) -> i32; // Might not match C types!
}
// DON'T: Assume sizes
#[repr(C)]
struct BadStruct {
count: i32, // C 'int' might not be 32 bits
size: i64, // C 'long' varies by platform!
ptr: usize, // size_t? intptr_t? Different!
}
```
## Good Example
```rust
use std::os::raw::{c_int, c_long, c_char, c_void};
// DO: Use std::os::raw types
extern "C" {
fn c_function(x: c_int, y: c_long) -> c_int;
}
// DO: Use libc for more types
use libc::{size_t, ssize_t, off_t, pid_t, time_t};
extern "C" {
fn read(fd: c_int, buf: *mut c_void, count: size_t) -> ssize_t;
fn lseek(fd: c_int, offset: off_t, whence: c_int) -> off_t;
fn getpid() -> pid_t;
}
// DO: Match C struct layout
#[repr(C)]
struct GoodStruct {
count: c_int,
size: c_long,
data: *mut c_void,
}
// DO: Use isize/usize for pointer-sized integers
#[repr(C)]
struct PointerSized {
offset: isize, // intptr_t equivalent
size: usize, // size_t in pointer arithmetic
}
```
## Type Mapping Reference
| C Type | Rust Type | Notes |
|--------|-----------|-------|
| `char` | `c_char` | May be signed or unsigned! |
| `signed char` | `i8` | |
| `unsigned char` | `u8` | |
| `short` | `c_short` | Usually i16 |
| `int` | `c_int` | Usually i32 |
| `long` | `c_long` | 32 or 64 bits! |
| `long long` | `c_longlong` | Usually i64 |
| `size_t` | `usize` or `libc::size_t` | |
| `ssize_t` | `isize` or `libc::ssize_t` | |
| `float` | `c_float` / `f32` | |
| `double` | `c_double` / `f64` | |
| `void*` | `*mut c_void` | |
| `const void*` | `*const c_void` | |
## Platform Differences
```rust
#[cfg(target_pointer_width = "64")]
type PtrDiff = i64;
#[cfg(target_pointer_width = "32")]
type PtrDiff = i32;
// Better: use isize
let diff: isize = ptr1 as isize - ptr2 as isize;
```
## Checklist
- [ ] Am I using std::os::raw or libc types for FFI?
- [ ] Have I avoided assuming c_long is 64 bits?
- [ ] Am I using size_t/usize for sizes?
- [ ] Have I tested on multiple platforms?
## Related Rules
- `ffi-13`: Ensure consistent data layout
- `ffi-14`: Types in FFI should have stable layout

View File

@@ -1,151 +0,0 @@
---
id: ffi-06
original_id: P.UNS.FFI.06
level: P
impact: HIGH
---
# Ensure C-ABI Compatibility for Strings Between Rust and C
## Summary
When passing strings across FFI, ensure both sides agree on encoding, null-termination, and memory ownership.
## Rationale
- Rust strings are UTF-8, C strings are byte arrays
- C expects null termination, Rust strings don't have it
- Memory ownership must be explicit to avoid leaks/double-frees
## String Passing Patterns
### Rust to C (Caller Allocates)
```rust
use std::ffi::CString;
use std::os::raw::c_char;
extern "C" {
fn c_process_string(s: *const c_char);
}
fn rust_to_c(s: &str) -> Result<(), std::ffi::NulError> {
let c_string = CString::new(s)?;
// c_string lives until end of scope
unsafe {
c_process_string(c_string.as_ptr());
}
// c_string dropped here, memory freed
Ok(())
}
```
### C to Rust (C Allocates, Rust Borrows)
```rust
use std::ffi::CStr;
use std::os::raw::c_char;
extern "C" {
fn c_get_string() -> *const c_char;
}
fn c_to_rust() -> Option<String> {
let ptr = unsafe { c_get_string() };
if ptr.is_null() {
return None;
}
// Borrow from C, don't take ownership
let c_str = unsafe { CStr::from_ptr(ptr) };
Some(c_str.to_string_lossy().into_owned())
}
```
### C to Rust (Ownership Transfer)
```rust
extern "C" {
fn c_create_string() -> *mut c_char;
fn c_free_string(s: *mut c_char);
}
struct CAllocatedString {
ptr: *mut c_char,
}
impl CAllocatedString {
fn new() -> Option<Self> {
let ptr = unsafe { c_create_string() };
if ptr.is_null() {
None
} else {
Some(Self { ptr })
}
}
fn as_str(&self) -> &str {
let c_str = unsafe { CStr::from_ptr(self.ptr) };
c_str.to_str().unwrap_or("")
}
}
impl Drop for CAllocatedString {
fn drop(&mut self) {
unsafe { c_free_string(self.ptr); }
}
}
```
### Rust to C (Ownership Transfer)
```rust
extern "C" {
fn c_take_ownership(s: *mut c_char); // C will free
}
fn give_to_c(s: &str) -> Result<(), std::ffi::NulError> {
let c_string = CString::new(s)?;
let ptr = c_string.into_raw(); // Don't drop CString
unsafe {
c_take_ownership(ptr);
// C now owns this memory
// To free it back in Rust: let _ = CString::from_raw(ptr);
}
Ok(())
}
```
## Encoding Considerations
```rust
// UTF-8 to platform encoding
use std::ffi::OsString;
use std::os::unix::ffi::OsStrExt;
fn to_platform_string(s: &str) -> CString {
// On Unix, UTF-8 usually works
CString::new(s).unwrap()
}
#[cfg(windows)]
fn to_wide_string(s: &str) -> Vec<u16> {
use std::os::windows::ffi::OsStrExt;
std::ffi::OsStr::new(s)
.encode_wide()
.chain(std::iter::once(0))
.collect()
}
```
## Checklist
- [ ] Is the string null-terminated when passed to C?
- [ ] Who allocates the memory? Who frees it?
- [ ] Is the encoding (UTF-8, ASCII, platform) documented?
- [ ] Am I handling conversion errors (interior nulls, invalid UTF-8)?
## Related Rules
- `ffi-01`: Use CString/CStr at FFI boundaries
- `ffi-02`: Read std::ffi documentation

View File

@@ -1,131 +0,0 @@
---
id: ffi-07
original_id: P.UNS.FFI.07
level: P
impact: HIGH
---
# Do Not Implement Drop for Types Passed to External Code
## Summary
If a type will be passed to external code that manages its lifetime, don't implement `Drop`. Otherwise, both Rust and the external code will try to free it.
## Rationale
- External code (C library) may take ownership of the data
- If Rust also tries to drop it, you get double-free
- Need clear ownership boundaries
## Bad Example
```rust
// DON'T: Drop on type that external code will free
#[repr(C)]
struct EventHandler {
callback: extern "C" fn(i32),
user_data: *mut c_void,
}
impl Drop for EventHandler {
fn drop(&mut self) {
// BAD: What if the C library already freed user_data?
unsafe { libc::free(self.user_data); }
}
}
extern "C" {
// C takes ownership and frees EventHandler when done
fn register_handler(h: *mut EventHandler);
}
fn bad_register() {
let handler = EventHandler { /* ... */ };
let ptr = Box::into_raw(Box::new(handler));
unsafe {
register_handler(ptr);
// If C code frees this, and Rust's Drop runs too = double-free
}
}
```
## Good Example
```rust
// DO: No Drop for types whose lifetime is managed externally
#[repr(C)]
struct EventHandler {
callback: extern "C" fn(i32),
user_data: *mut c_void,
}
// No Drop impl - C library manages lifetime
extern "C" {
fn register_handler(h: *mut EventHandler);
fn unregister_handler(h: *mut EventHandler);
}
// DO: Wrap in a Rust type that knows when it's safe to drop
struct RegisteredHandler {
ptr: *mut EventHandler,
registered: bool,
}
impl RegisteredHandler {
fn register(handler: EventHandler) -> Self {
let ptr = Box::into_raw(Box::new(handler));
unsafe { register_handler(ptr); }
Self { ptr, registered: true }
}
fn unregister(&mut self) {
if self.registered {
unsafe { unregister_handler(self.ptr); }
self.registered = false;
}
}
}
impl Drop for RegisteredHandler {
fn drop(&mut self) {
self.unregister();
// Only free if we still own it
if !self.registered {
unsafe { drop(Box::from_raw(self.ptr)); }
}
}
}
// DO: Use ManuallyDrop for explicit control
use std::mem::ManuallyDrop;
fn explicit_ownership() {
let handler = ManuallyDrop::new(EventHandler { /* ... */ });
let ptr = &*handler as *const EventHandler as *mut EventHandler;
unsafe {
register_handler(ptr);
// C now owns handler, don't drop it in Rust
}
}
```
## Ownership Patterns
| Pattern | Who Owns | Rust Drop? |
|---------|----------|------------|
| Rust creates, Rust frees | Rust | Yes |
| Rust creates, C frees | C | No |
| C creates, C frees | C | No (use wrapper) |
| C creates, Rust frees | Rust | Yes (in wrapper) |
## Checklist
- [ ] Who will free this type's memory?
- [ ] If external code frees it, am I avoiding Drop?
- [ ] If ownership is conditional, do I track it?
- [ ] Am I using ManuallyDrop or forget() when transferring ownership?
## Related Rules
- `ffi-03`: Implement Drop for wrapped C pointers (opposite case)
- `mem-03`: Don't let String/Vec drop foreign memory

View File

@@ -1,146 +0,0 @@
---
id: ffi-08
original_id: P.UNS.FFI.08
level: P
impact: HIGH
---
# Handle Errors Properly in FFI
## Summary
FFI functions must use C-compatible error handling (return codes, errno, out parameters). Rust's Result/Option don't cross FFI boundaries.
## Rationale
- C doesn't have Result or Option
- Exceptions don't exist in C
- Must use patterns C code understands
## Bad Example
```rust
// DON'T: Return Result across FFI
#[no_mangle]
pub extern "C" fn bad_open(path: *const c_char) -> Result<Handle, Error> {
// Result is not C-compatible!
unimplemented!()
}
// DON'T: Return Option across FFI
#[no_mangle]
pub extern "C" fn bad_find(id: i32) -> Option<*mut Data> {
// Option<*mut T> might work but is confusing
unimplemented!()
}
```
## Good Example
```rust
use std::os::raw::{c_char, c_int};
// Error codes
const SUCCESS: c_int = 0;
const ERR_NULL_PTR: c_int = 1;
const ERR_INVALID_PATH: c_int = 2;
const ERR_FILE_NOT_FOUND: c_int = 3;
const ERR_PERMISSION: c_int = 4;
const ERR_UNKNOWN: c_int = -1;
// DO: Return error code, output via pointer
#[no_mangle]
pub extern "C" fn open_file(
path: *const c_char,
out_handle: *mut *mut Handle
) -> c_int {
if path.is_null() || out_handle.is_null() {
return ERR_NULL_PTR;
}
let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
match File::open(path_str) {
Ok(file) => {
let handle = Box::into_raw(Box::new(Handle { file }));
unsafe { *out_handle = handle; }
SUCCESS
}
Err(e) => {
match e.kind() {
std::io::ErrorKind::NotFound => ERR_FILE_NOT_FOUND,
std::io::ErrorKind::PermissionDenied => ERR_PERMISSION,
_ => ERR_UNKNOWN,
}
}
}
}
// DO: Use errno for POSIX-style APIs
#[cfg(unix)]
#[no_mangle]
pub extern "C" fn posix_style_read(
fd: c_int,
buf: *mut u8,
count: usize
) -> isize {
if buf.is_null() {
unsafe { *libc::__errno_location() = libc::EINVAL; }
return -1;
}
// ... do read ...
// On error:
// unsafe { *libc::__errno_location() = error_code; }
// return -1;
count as isize
}
// DO: Provide error message function
thread_local! {
static LAST_ERROR: std::cell::RefCell<Option<String>> = std::cell::RefCell::new(None);
}
#[no_mangle]
pub extern "C" fn get_error_message(buf: *mut c_char, len: usize) -> c_int {
LAST_ERROR.with(|e| {
if let Some(msg) = e.borrow().as_ref() {
let bytes = msg.as_bytes();
let copy_len = std::cmp::min(bytes.len(), len.saturating_sub(1));
unsafe {
std::ptr::copy_nonoverlapping(bytes.as_ptr(), buf as *mut u8, copy_len);
*buf.add(copy_len) = 0;
}
SUCCESS
} else {
ERR_UNKNOWN
}
})
}
```
## Error Handling Patterns
| Pattern | Usage |
|---------|-------|
| Return code | Simple success/failure |
| Return code + out param | Return value on success |
| errno | POSIX-style APIs |
| Error message function | Detailed error info |
| Last-error thread-local | Windows-style APIs |
## Checklist
- [ ] Am I returning C-compatible error indicators?
- [ ] Are output parameters used for return values?
- [ ] Is there a way to get detailed error info?
- [ ] Am I documenting all possible error codes?
## Related Rules
- `ffi-04`: Handle panics at FFI boundary
- `safety-10`: Document safety requirements

View File

@@ -1,136 +0,0 @@
---
id: ffi-09
original_id: P.UNS.FFI.09
level: P
impact: MEDIUM
---
# Use References Instead of Raw Pointers When Calling Safe C Functions
## Summary
When wrapping C functions that don't need null pointers, use Rust references in the safe wrapper to enforce non-null at compile time.
## Rationale
- References guarantee non-null
- References have lifetime tracking
- Raw pointers should stay in the unsafe FFI layer
- Safe Rust API should use safe types
## Bad Example
```rust
extern "C" {
fn c_process(data: *const u8, len: usize);
}
// DON'T: Expose raw pointers in safe API
pub fn process(data: *const u8, len: usize) {
// Caller might pass null!
unsafe { c_process(data, len); }
}
// DON'T: Unsafe function when it could be safe
pub unsafe fn process_unsafe(data: *const u8, len: usize) {
// Why force caller to use unsafe?
c_process(data, len);
}
```
## Good Example
```rust
extern "C" {
fn c_process(data: *const u8, len: usize);
fn c_modify(data: *mut Data);
fn c_optional(data: *const Data); // Can be null
}
// DO: Use slice reference for safe API
pub fn process(data: &[u8]) {
// Reference guarantees non-null
// Slice guarantees valid length
unsafe { c_process(data.as_ptr(), data.len()); }
}
// DO: Use &mut for exclusive access
pub fn modify(data: &mut Data) {
// Mutable reference guarantees:
// - Non-null
// - Exclusive access
// - Valid for duration
unsafe { c_modify(data as *mut Data); }
}
// DO: Use Option<&T> for nullable parameters
pub fn optional(data: Option<&Data>) {
let ptr = data.map(|d| d as *const Data).unwrap_or(std::ptr::null());
unsafe { c_optional(ptr); }
}
// DO: Wrap FFI types in safe Rust types
pub struct SafeHandle(*mut c_void);
impl SafeHandle {
pub fn new() -> Option<Self> {
let ptr = unsafe { create_handle() };
if ptr.is_null() {
None
} else {
Some(Self(ptr))
}
}
// Methods take &self or &mut self, not raw pointers
pub fn do_something(&self) {
unsafe { handle_operation(self.0); }
}
}
```
## Converting Between References and Pointers
```rust
// Reference to pointer
fn ref_to_ptr(r: &Data) -> *const Data {
r as *const Data
}
fn mut_ref_to_ptr(r: &mut Data) -> *mut Data {
r as *mut Data
}
// Slice to pointer
fn slice_to_ptr(s: &[u8]) -> (*const u8, usize) {
(s.as_ptr(), s.len())
}
// Pointer to reference (unsafe)
unsafe fn ptr_to_ref<'a>(p: *const Data) -> &'a Data {
&*p
}
unsafe fn ptr_to_mut<'a>(p: *mut Data) -> &'a mut Data {
&mut *p
}
```
## When to Use Raw Pointers
- FFI declarations (`extern "C"`)
- Implementing the unsafe boundary layer
- When null is a valid value
- When the pointee might not be valid Rust (e.g., uninitialized)
## Checklist
- [ ] Can this parameter be a reference instead of a pointer?
- [ ] Am I checking for null in the unsafe layer?
- [ ] Is the safe API free of raw pointers?
- [ ] Do I use Option<&T> for nullable references?
## Related Rules
- `safety-06`: Don't expose raw pointers in public APIs
- `ffi-02`: Read std::ffi documentation

View File

@@ -1,132 +0,0 @@
---
id: ffi-10
original_id: P.UNS.FFI.10
level: P
impact: CRITICAL
---
# Exported Rust Functions Must Be Designed for Thread-Safety
## Summary
Functions exported to C with `#[no_mangle] extern "C"` may be called from multiple threads. Ensure they are thread-safe.
## Rationale
- C code doesn't know about Rust's thread safety guarantees
- C may call your function from any thread
- Global state must be synchronized
- Race conditions are undefined behavior
## Bad Example
```rust
// DON'T: Unsynchronized global state
static mut COUNTER: i32 = 0;
#[no_mangle]
pub extern "C" fn increment() -> i32 {
unsafe {
COUNTER += 1; // Data race if called from multiple threads!
COUNTER
}
}
// DON'T: Thread-local assuming single thread
thread_local! {
static CONFIG: RefCell<Config> = RefCell::new(Config::default());
}
#[no_mangle]
pub extern "C" fn set_config(value: i32) {
// Different threads get different configs!
// Is that what the C caller expects?
CONFIG.with(|c| c.borrow_mut().value = value);
}
// DON'T: Non-Send types in globals
static mut HANDLE: Option<Rc<Data>> = None; // Rc is not Send!
```
## Good Example
```rust
use std::sync::atomic::{AtomicI32, Ordering};
use std::sync::{Mutex, OnceLock};
// DO: Use atomics for simple counters
static COUNTER: AtomicI32 = AtomicI32::new(0);
#[no_mangle]
pub extern "C" fn increment() -> i32 {
COUNTER.fetch_add(1, Ordering::SeqCst) + 1
}
// DO: Use Mutex for complex state
static CONFIG: OnceLock<Mutex<Config>> = OnceLock::new();
fn get_config() -> &'static Mutex<Config> {
CONFIG.get_or_init(|| Mutex::new(Config::default()))
}
#[no_mangle]
pub extern "C" fn set_config_value(value: i32) -> i32 {
match get_config().lock() {
Ok(mut config) => {
config.value = value;
0 // Success
}
Err(_) => -1 // Lock poisoned
}
}
// DO: Document thread safety requirements
/// Initializes the library. NOT thread-safe.
/// Must be called once from main thread before any other function.
#[no_mangle]
pub extern "C" fn init() -> i32 {
// One-time initialization
0
}
/// Processes data. Thread-safe.
/// May be called from multiple threads concurrently.
#[no_mangle]
pub extern "C" fn process(data: *const u8, len: usize) -> i32 {
// Uses only local state or synchronized globals
0
}
// DO: Make non-thread-safe APIs explicit
/// Handle for single-threaded use only.
///
/// # Thread Safety
///
/// This handle must only be used from the thread that created it.
struct SingleThreadHandle {
data: *mut Data,
_not_send: std::marker::PhantomData<*const ()>, // !Send
}
```
## Synchronization Patterns
| Pattern | Use Case |
|---------|----------|
| `AtomicT` | Simple counters, flags |
| `Mutex<T>` | Complex shared state |
| `RwLock<T>` | Read-heavy shared state |
| `OnceLock<T>` | Lazy one-time init |
| `thread_local!` | Per-thread state (document!) |
## Checklist
- [ ] Does my exported function access global state?
- [ ] Is that state properly synchronized?
- [ ] Have I documented thread-safety guarantees?
- [ ] Are any types !Send/!Sync exposed across FFI?
## Related Rules
- `ptr-01`: Don't share raw pointers across threads
- `safety-05`: Send/Sync implementation safety

View File

@@ -1,142 +0,0 @@
---
id: ffi-11
original_id: P.UNS.FFI.11
level: P
impact: HIGH
clippy: unaligned_references
---
# Be Careful with UB When Referencing #[repr(packed)] Struct Fields
## Summary
Creating references to fields in `#[repr(packed)]` structs is undefined behavior if the field is misaligned. Use raw pointers and `read_unaligned`/`write_unaligned` instead.
## Rationale
- Packed structs have no padding, so fields may be misaligned
- References must be aligned; misaligned references are UB
- Even implicit references (method calls, match) can cause UB
## Bad Example
```rust
#[repr(C, packed)]
struct Packet {
header: u8,
value: u32, // Misaligned! At offset 1, not 4
data: u64, // Misaligned! At offset 5, not 8
}
fn bad_reference(p: &Packet) -> &u32 {
&p.value // UB: Creates misaligned reference!
}
fn bad_match(p: &Packet) {
match p.value { // UB: Match creates a reference
0 => {},
_ => {},
}
}
fn bad_method(p: &Packet) {
p.value.to_string(); // UB: Method call creates reference
}
fn bad_borrow(p: &mut Packet) {
let v = &mut p.value; // UB: Misaligned mutable reference
*v = 42;
}
```
## Good Example
```rust
#[repr(C, packed)]
struct Packet {
header: u8,
value: u32,
data: u64,
}
// DO: Copy out the value
fn good_read(p: &Packet) -> u32 {
p.value // Copies the value, no reference created
}
// DO: Use addr_of! for raw pointer (Rust 2021+)
fn good_ptr_read(p: &Packet) -> u32 {
// SAFETY: read_unaligned handles misalignment
unsafe {
std::ptr::addr_of!(p.value).read_unaligned()
}
}
// DO: Use addr_of_mut! for writing
fn good_ptr_write(p: &mut Packet, value: u32) {
// SAFETY: write_unaligned handles misalignment
unsafe {
std::ptr::addr_of_mut!(p.value).write_unaligned(value);
}
}
// DO: Create accessor methods
impl Packet {
fn value(&self) -> u32 {
unsafe { std::ptr::addr_of!(self.value).read_unaligned() }
}
fn set_value(&mut self, value: u32) {
unsafe { std::ptr::addr_of_mut!(self.value).write_unaligned(value); }
}
fn data(&self) -> u64 {
unsafe { std::ptr::addr_of!(self.data).read_unaligned() }
}
}
// DO: Consider using byte arrays + from_ne_bytes
#[repr(C, packed)]
struct PacketBytes {
header: u8,
value: [u8; 4], // Store as bytes
data: [u8; 8],
}
impl PacketBytes {
fn value(&self) -> u32 {
u32::from_ne_bytes(self.value) // Safe, no alignment issue
}
}
```
## Safe Alternatives
```rust
// Alternative 1: Don't use packed
#[repr(C)]
struct AlignedPacket {
header: u8,
_pad: [u8; 3],
value: u32,
data: u64,
}
// Alternative 2: Use zerocopy crate
// use zerocopy::{AsBytes, FromBytes};
// Alternative 3: Use bytemuck
// use bytemuck::{Pod, Zeroable};
```
## Checklist
- [ ] Am I creating references to packed struct fields?
- [ ] Am I using addr_of! / addr_of_mut! for field access?
- [ ] Am I using read_unaligned / write_unaligned?
- [ ] Would a byte array representation be safer?
## Related Rules
- `ptr-04`: Don't dereference misaligned pointers
- `mem-01`: Choose appropriate data layout

View File

@@ -1,164 +0,0 @@
---
id: ffi-12
original_id: P.UNS.FFI.12
level: P
impact: MEDIUM
---
# Document Invariant Assumptions for C-Provided Parameters
## Summary
When receiving parameters from C, document what invariants you assume (non-null, alignment, validity, lifetime) and verify them when possible.
## Rationale
- C doesn't enforce invariants at compile time
- Rust code needs to validate or document assumptions
- Debugging FFI bugs is hard without clear documentation
## Bad Example
```rust
// DON'T: Undocumented assumptions
extern "C" {
fn get_data() -> *mut Data;
}
fn bad_use() -> &'static Data {
let ptr = unsafe { get_data() };
// Assumes:
// - ptr is non-null (not documented)
// - ptr is aligned (not checked)
// - Data is valid (not verified)
// - Lifetime is 'static (just guessing)
unsafe { &*ptr }
}
// DON'T: Silent assumptions in function signature
#[no_mangle]
pub extern "C" fn process(data: *const Data, len: usize) {
// What if data is null?
// What if len is wrong?
// What if data contains invalid Data?
let slice = unsafe {
std::slice::from_raw_parts(data, len)
};
}
```
## Good Example
```rust
/// Retrieves data from the C library.
///
/// # Invariants Assumed from C
///
/// - Returns a non-null pointer on success, null on failure
/// - Returned pointer is valid for the lifetime of the library
/// - Returned pointer is aligned for `Data`
/// - The `Data` struct is fully initialized
extern "C" {
fn get_data() -> *mut Data;
}
fn documented_use() -> Option<&'static Data> {
let ptr = unsafe { get_data() };
// Verify what we can
if ptr.is_null() {
return None;
}
// Document what we can't verify
// SAFETY:
// - Non-null: checked above
// - Aligned: documented in C library docs
// - Valid: C library guarantees initialized Data
// - Lifetime: C library guarantees static lifetime
Some(unsafe { &*ptr })
}
/// Processes data provided by C caller.
///
/// # Parameters
///
/// - `data`: Must be non-null, aligned for `Data`, and point to `len` valid `Data` items
/// - `len`: Number of items. Must not exceed `isize::MAX / size_of::<Data>()`
///
/// # Returns
///
/// - `0` on success
/// - `-1` if `data` is null
/// - `-2` if `len` is invalid
///
/// # Thread Safety
///
/// This function is thread-safe. The `data` array must not be mutated during the call.
#[no_mangle]
pub extern "C" fn process_documented(data: *const Data, len: usize) -> i32 {
// Verify invariants we can check
if data.is_null() {
return -1;
}
if len > isize::MAX as usize / std::mem::size_of::<Data>() {
return -2;
}
// SAFETY:
// - Non-null: checked above
// - Aligned: documented requirement for caller
// - Valid for len items: documented requirement for caller
// - Not mutated: documented thread safety requirement
let slice = unsafe { std::slice::from_raw_parts(data, len) };
for item in slice {
// process...
}
0
}
```
## Documentation Template
```rust
/// Brief description.
///
/// # Parameters
///
/// - `param`: Description, constraints (non-null, aligned, etc.)
///
/// # Invariants Assumed
///
/// The following invariants are assumed and NOT verified:
/// - Invariant 1: explanation
/// - Invariant 2: explanation
///
/// The following invariants ARE verified at runtime:
/// - Verified 1: how it's checked
///
/// # Safety (for unsafe fn)
///
/// Caller must ensure:
/// - Requirement 1
/// - Requirement 2
///
/// # Errors
///
/// Returns error code when:
/// - Condition 1: error code
```
## Checklist
- [ ] Have I documented all assumptions about C parameters?
- [ ] Which invariants can I verify at runtime?
- [ ] Which must I trust the C caller to uphold?
- [ ] Have I documented error conditions and return values?
## Related Rules
- `safety-02`: Verify safety invariants
- `safety-10`: Document safety requirements

View File

@@ -1,146 +0,0 @@
---
id: ffi-13
original_id: P.UNS.FFI.13
level: P
impact: HIGH
---
# Ensure Consistent Data Layout for Custom Types
## Summary
Types shared between Rust and C must have `#[repr(C)]` to ensure the memory layout matches what C expects.
## Rationale
- Rust's default layout is unspecified and may change
- C has specific, standardized layout rules
- Mismatched layouts cause memory corruption
## Bad Example
```rust
// DON'T: Rust layout for FFI types
struct BadStruct {
a: u8,
b: u32,
c: u8,
}
// Rust may reorder to: b, a, c (for better packing)
// C expects: a, padding, b, c, padding
extern "C" {
fn use_struct(s: *const BadStruct); // Layout mismatch!
}
// DON'T: Assume Rust enum layout matches C
enum BadEnum {
A,
B(i32),
C { x: u8, y: u8 },
}
// Rust enum layout is complex and not C-compatible
```
## Good Example
```rust
// DO: Use repr(C) for FFI structs
#[repr(C)]
struct GoodStruct {
a: u8, // offset 0
// 3 bytes padding
b: u32, // offset 4
c: u8, // offset 8
// 3 bytes padding
}
// Total size: 12, align: 4
// DO: Use repr(C) for enums with explicit discriminant
#[repr(C)]
enum GoodEnum {
A = 0,
B = 1,
C = 2,
}
// Equivalent to C: enum { A = 0, B = 1, C = 2 };
// DO: For complex enums, use tagged unions
#[repr(C)]
struct TaggedUnion {
tag: GoodEnum,
data: GoodUnionData,
}
#[repr(C)]
union GoodUnionData {
a: (), // For GoodEnum::A
b: i32, // For GoodEnum::B
c: [u8; 2], // For GoodEnum::C
}
// DO: Verify layout at compile time
const _: () = {
assert!(std::mem::size_of::<GoodStruct>() == 12);
assert!(std::mem::align_of::<GoodStruct>() == 4);
};
```
## Layout Verification
```rust
use std::mem::{size_of, align_of, offset_of};
#[repr(C)]
struct Verified {
a: u8,
b: u32,
c: u8,
}
// Compile-time layout verification
const _: () = {
assert!(size_of::<Verified>() == 12);
assert!(align_of::<Verified>() == 4);
// offset_of! requires nightly or crate
// assert!(offset_of!(Verified, a) == 0);
// assert!(offset_of!(Verified, b) == 4);
// assert!(offset_of!(Verified, c) == 8);
};
// Runtime verification
#[test]
fn verify_layout() {
assert_eq!(size_of::<Verified>(), 12);
assert_eq!(align_of::<Verified>(), 4);
let v = Verified { a: 0, b: 0, c: 0 };
let base = &v as *const _ as usize;
assert_eq!(&v.a as *const _ as usize - base, 0);
assert_eq!(&v.b as *const _ as usize - base, 4);
assert_eq!(&v.c as *const _ as usize - base, 8);
}
```
## repr Options
| Attribute | Effect |
|-----------|--------|
| `#[repr(C)]` | C-compatible layout |
| `#[repr(C, packed)]` | C layout, no padding |
| `#[repr(C, align(N))]` | C layout, minimum align N |
| `#[repr(transparent)]` | Same layout as single field |
| `#[repr(u8)]` etc. | Enum discriminant type |
## Checklist
- [ ] Is every FFI struct marked `#[repr(C)]`?
- [ ] Is every FFI enum using explicit discriminants?
- [ ] Have I verified the layout matches the C header?
- [ ] Have I added compile-time assertions?
## Related Rules
- `mem-01`: Choose appropriate data layout
- `ffi-14`: Types in FFI should have stable layout

View File

@@ -1,135 +0,0 @@
---
id: ffi-14
original_id: P.UNS.FFI.14
level: P
impact: HIGH
---
# Types Used in FFI Should Have Stable Layout
## Summary
FFI types should not change layout between versions. Use `#[repr(C)]` and avoid types with unstable layout like generic `std` types.
## Rationale
- ABI compatibility requires stable layout
- Dynamic libraries may be loaded with different compiler versions
- Layout changes break binary compatibility
## Bad Example
```rust
// DON'T: Use Rust std types with unstable layout in FFI
extern "C" {
// Vec layout is not stable!
fn bad_vec(v: Vec<i32>);
// String layout is not stable!
fn bad_string(s: String);
// HashMap layout varies between versions
fn bad_map(m: std::collections::HashMap<i32, i32>);
}
// DON'T: Use Rust-specific types in C structs
#[repr(C)]
struct BadMixed {
id: i32,
data: Vec<u8>, // Vec is not C-compatible!
}
// DON'T: Use Option with non-null optimization assumptions
#[repr(C)]
struct BadOption {
value: Option<std::num::NonZeroU32>, // Layout may change!
}
```
## Good Example
```rust
use std::os::raw::{c_int, c_char, c_void};
// DO: Use C-compatible types
#[repr(C)]
struct GoodStruct {
id: c_int,
name: *const c_char, // C-style string
data: *const c_void, // Generic pointer
data_len: usize,
}
// DO: Use explicit struct for what Vec would provide
#[repr(C)]
struct GoodBuffer {
ptr: *mut u8,
len: usize,
cap: usize,
}
impl GoodBuffer {
fn from_vec(mut v: Vec<u8>) -> Self {
let buf = Self {
ptr: v.as_mut_ptr(),
len: v.len(),
cap: v.capacity(),
};
std::mem::forget(v);
buf
}
/// # Safety
/// Must have been created by from_vec()
unsafe fn into_vec(self) -> Vec<u8> {
Vec::from_raw_parts(self.ptr, self.len, self.cap)
}
}
// DO: Use fixed-size arrays for bounded data
#[repr(C)]
struct FixedName {
name: [c_char; 64],
name_len: usize,
}
// DO: Define your own stable option type
#[repr(C)]
struct OptionalU32 {
has_value: bool,
value: u32,
}
impl From<Option<u32>> for OptionalU32 {
fn from(opt: Option<u32>) -> Self {
match opt {
Some(v) => Self { has_value: true, value: v },
None => Self { has_value: false, value: 0 },
}
}
}
```
## Stable Types for FFI
| Use Instead Of | Stable Type |
|----------------|-------------|
| `Vec<T>` | `*mut T` + `len` + `cap` |
| `String` | `*const c_char` or `*mut c_char` + `len` |
| `&[T]` | `*const T` + `len` |
| `Option<T>` | Custom tagged struct |
| `Result<T, E>` | Error code + out parameter |
| `Box<T>` | `*mut T` |
| `bool` | `c_int` or explicit `u8` |
## Checklist
- [ ] Am I using only C-compatible primitive types?
- [ ] Am I avoiding std collection types in FFI signatures?
- [ ] Have I created stable wrappers for Rust types?
- [ ] Is the layout documented for other languages?
## Related Rules
- `ffi-13`: Ensure consistent data layout
- `ffi-05`: Use portable type aliases

View File

@@ -1,145 +0,0 @@
---
id: ffi-15
original_id: P.UNS.FFI.15
level: P
impact: HIGH
---
# Validate Non-Robust External Values
## Summary
Data received from external sources (FFI, files, network) may be invalid. Validate before using it as Rust types with stricter invariants.
## Rationale
- External data can be malicious or corrupted
- Rust types have invariants (e.g., valid UTF-8 for str)
- Invalid data causes undefined behavior
## Bad Example
```rust
// DON'T: Trust external data
extern "C" {
fn get_status() -> u8;
}
#[derive(Debug)]
enum Status { Active = 0, Inactive = 1, Pending = 2 }
fn bad_convert() -> Status {
let raw = unsafe { get_status() };
// BAD: Assumes C returns valid enum value
unsafe { std::mem::transmute(raw) } // UB if raw > 2
}
// DON'T: Trust strings from C
fn bad_string(ptr: *const c_char) -> &str {
let cstr = unsafe { CStr::from_ptr(ptr) };
// BAD: Assumes valid UTF-8
cstr.to_str().unwrap()
}
// DON'T: Trust size values
fn bad_size(ptr: *const u8, len: usize) -> Vec<u8> {
// BAD: len could be huge, causing OOM
// BAD: len could exceed actual data
unsafe { std::slice::from_raw_parts(ptr, len) }.to_vec()
}
```
## Good Example
```rust
// DO: Validate enum values
#[derive(Debug, Clone, Copy)]
#[repr(u8)]
enum Status {
Active = 0,
Inactive = 1,
Pending = 2,
}
impl TryFrom<u8> for Status {
type Error = InvalidStatusError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(Status::Active),
1 => Ok(Status::Inactive),
2 => Ok(Status::Pending),
_ => Err(InvalidStatusError(value)),
}
}
}
fn good_convert() -> Result<Status, InvalidStatusError> {
let raw = unsafe { get_status() };
Status::try_from(raw) // Returns error for invalid values
}
// DO: Handle invalid UTF-8
fn good_string(ptr: *const c_char) -> Result<String, std::str::Utf8Error> {
if ptr.is_null() {
return Ok(String::new());
}
let cstr = unsafe { CStr::from_ptr(ptr) };
cstr.to_str().map(|s| s.to_owned())
}
fn good_string_lossy(ptr: *const c_char) -> String {
if ptr.is_null() {
return String::new();
}
let cstr = unsafe { CStr::from_ptr(ptr) };
cstr.to_string_lossy().into_owned() // Replaces invalid UTF-8
}
// DO: Validate sizes
const MAX_REASONABLE_SIZE: usize = 100 * 1024 * 1024; // 100 MB
fn good_size(ptr: *const u8, len: usize) -> Result<Vec<u8>, ValidationError> {
if ptr.is_null() {
return Err(ValidationError::NullPointer);
}
if len > MAX_REASONABLE_SIZE {
return Err(ValidationError::SizeTooLarge);
}
// Still need to trust that ptr points to len valid bytes
// Document this as a caller requirement
let slice = unsafe { std::slice::from_raw_parts(ptr, len) };
Ok(slice.to_vec())
}
// DO: Use num_enum for safe enum conversion
// use num_enum::TryFromPrimitive;
//
// #[derive(TryFromPrimitive)]
// #[repr(u8)]
// enum Status { Active = 0, Inactive = 1, Pending = 2 }
```
## Validation Patterns
| External Data | Validation |
|---------------|------------|
| Enum discriminant | Match against valid values |
| String | Check UTF-8 or use lossy conversion |
| Size/length | Check against maximum |
| Pointer | Check for null |
| Boolean | Explicit 0/1 check or treat any non-zero as true |
| Float | Check for NaN, infinity if problematic |
## Checklist
- [ ] Am I validating external enum values?
- [ ] Am I handling potential invalid UTF-8?
- [ ] Am I checking sizes against reasonable limits?
- [ ] Am I using TryFrom instead of transmute?
## Related Rules
- `ffi-12`: Document invariant assumptions
- `safety-02`: Verify safety invariants

View File

@@ -1,141 +0,0 @@
---
id: ffi-16
original_id: P.UNS.FFI.16
level: P
impact: HIGH
---
# Separate Data and Code When Passing Rust Closures to C
## Summary
C callbacks are function pointers without captured state. To pass Rust closures to C, separate the function pointer from the closure data using a "trampoline" pattern.
## Rationale
- Rust closures can capture state (like lambdas)
- C function pointers are just addresses, no state
- Must pass state separately via `void*` user_data
## Bad Example
```rust
// DON'T: Try to pass closure directly
extern "C" {
fn set_callback(cb: fn(i32) -> i32); // Only works for non-capturing!
}
fn bad_closure() {
let multiplier = 2;
let closure = |x| x * multiplier; // Captures multiplier
// This won't compile - closure is not fn pointer
// set_callback(closure);
}
// DON'T: Transmute closure to function pointer
fn bad_transmute() {
let closure = |x: i32| x * 2;
let fp: fn(i32) -> i32 = unsafe { std::mem::transmute(closure) };
// UB: Closure may have non-zero size
}
```
## Good Example
```rust
use std::os::raw::c_void;
use std::ffi::c_int;
// C callback signature with user_data
type CCallback = extern "C" fn(value: c_int, user_data: *mut c_void) -> c_int;
extern "C" {
fn set_callback(cb: CCallback, user_data: *mut c_void);
fn remove_callback();
}
// DO: Use trampoline pattern
fn good_closure<F: FnMut(i32) -> i32>(mut closure: F) {
// Trampoline function that forwards to the closure
extern "C" fn trampoline<F: FnMut(i32) -> i32>(
value: c_int,
user_data: *mut c_void,
) -> c_int {
let closure = unsafe { &mut *(user_data as *mut F) };
closure(value as i32) as c_int
}
let user_data = &mut closure as *mut F as *mut c_void;
unsafe {
set_callback(trampoline::<F>, user_data);
// Important: closure must live until callback is removed!
}
}
// DO: Box the closure for 'static lifetime
struct CallbackHandle {
closure: Box<dyn FnMut(i32) -> i32>,
}
impl CallbackHandle {
fn new<F: FnMut(i32) -> i32 + 'static>(closure: F) -> Self {
Self { closure: Box::new(closure) }
}
fn register(&mut self) {
extern "C" fn trampoline(value: c_int, user_data: *mut c_void) -> c_int {
let closure = unsafe { &mut *(user_data as *mut Box<dyn FnMut(i32) -> i32>) };
closure(value as i32) as c_int
}
let user_data = &mut self.closure as *mut _ as *mut c_void;
unsafe { set_callback(trampoline, user_data); }
}
}
impl Drop for CallbackHandle {
fn drop(&mut self) {
unsafe { remove_callback(); }
// Now safe to drop closure
}
}
// Usage
fn example() {
let multiplier = 2;
let mut handle = CallbackHandle::new(move |x| x * multiplier);
handle.register();
// handle must live until callback is no longer needed
}
```
## Trampoline Pattern
```
Rust Closure: |x| x * captured_value
|
v
+-----------------+ +-----------------+
| trampoline fn | --> | closure data |
| (no captures) | | (captured_value)|
+-----------------+ +-----------------+
| ^
| user_data ptr |
+-------------------------+
C sees: function pointer + void* user_data
```
## Checklist
- [ ] Does my closure capture any state?
- [ ] Am I using the trampoline pattern?
- [ ] Does the closure data live long enough?
- [ ] Am I unregistering before dropping the closure?
## Related Rules
- `ffi-03`: Implement Drop for resource wrappers
- `ffi-10`: Thread safety for callbacks

View File

@@ -1,152 +0,0 @@
---
id: ffi-17
original_id: P.UNS.FFI.17
level: P
impact: MEDIUM
---
# Use Dedicated Opaque Type Pointers Instead of c_void for C Opaque Types
## Summary
Instead of using `*mut c_void` for opaque C handles, create dedicated marker types that provide type safety.
## Rationale
- `*mut c_void` accepts any pointer, easy to mix up handles
- Dedicated types catch mistakes at compile time
- Self-documenting code
- Prevents accidental use of wrong free function
## Bad Example
```rust
use std::ffi::c_void;
extern "C" {
fn create_database() -> *mut c_void;
fn create_connection() -> *mut c_void;
fn execute(conn: *mut c_void, query: *const i8);
fn close_database(db: *mut c_void);
fn close_connection(conn: *mut c_void);
}
fn bad_usage() {
let db = unsafe { create_database() };
let conn = unsafe { create_connection() };
// BUG: Passed db where conn was expected - compiles fine!
unsafe { execute(db, b"SELECT 1\0".as_ptr() as *const i8) };
// BUG: Wrong close function - compiles fine!
unsafe { close_connection(db) };
unsafe { close_database(conn) };
}
```
## Good Example
```rust
use std::marker::PhantomData;
// DO: Define opaque marker types
#[repr(C)]
pub struct Database {
_private: [u8; 0],
_marker: PhantomData<(*mut u8, std::marker::PhantomPinned)>,
}
#[repr(C)]
pub struct Connection {
_private: [u8; 0],
_marker: PhantomData<(*mut u8, std::marker::PhantomPinned)>,
}
extern "C" {
fn create_database() -> *mut Database;
fn create_connection(db: *mut Database) -> *mut Connection;
fn execute(conn: *mut Connection, query: *const i8) -> i32;
fn close_database(db: *mut Database);
fn close_connection(conn: *mut Connection);
}
fn good_usage() {
let db = unsafe { create_database() };
let conn = unsafe { create_connection(db) };
// Compile error: expected *mut Connection, found *mut Database
// unsafe { execute(db, b"SELECT 1\0".as_ptr() as *const i8) };
// Correct usage
unsafe { execute(conn, b"SELECT 1\0".as_ptr() as *const i8) };
unsafe { close_connection(conn) };
unsafe { close_database(db) };
}
// DO: Wrap in safe Rust types
pub struct SafeDatabase {
ptr: *mut Database,
}
impl SafeDatabase {
pub fn new() -> Option<Self> {
let ptr = unsafe { create_database() };
if ptr.is_null() { None } else { Some(Self { ptr }) }
}
pub fn connect(&self) -> Option<SafeConnection<'_>> {
let ptr = unsafe { create_connection(self.ptr) };
if ptr.is_null() { None } else { Some(SafeConnection { ptr, _db: PhantomData }) }
}
}
impl Drop for SafeDatabase {
fn drop(&mut self) {
unsafe { close_database(self.ptr); }
}
}
pub struct SafeConnection<'db> {
ptr: *mut Connection,
_db: PhantomData<&'db SafeDatabase>,
}
impl SafeConnection<'_> {
pub fn execute(&self, query: &str) -> Result<(), ()> {
let query = std::ffi::CString::new(query).map_err(|_| ())?;
let result = unsafe { execute(self.ptr, query.as_ptr()) };
if result == 0 { Ok(()) } else { Err(()) }
}
}
impl Drop for SafeConnection<'_> {
fn drop(&mut self) {
unsafe { close_connection(self.ptr); }
}
}
```
## Opaque Type Pattern
```rust
// The zero-sized array makes it impossible to construct
// PhantomData ensures proper variance and !Send/!Sync if needed
#[repr(C)]
pub struct OpaqueHandle {
_private: [u8; 0],
_marker: PhantomData<(*mut u8, std::marker::PhantomPinned)>,
}
```
## Checklist
- [ ] Am I using `*mut c_void` for distinct handle types?
- [ ] Would dedicated types prevent bugs?
- [ ] Have I wrapped opaque pointers in safe Rust types?
- [ ] Do my types enforce correct handle/function pairing?
## Related Rules
- `ffi-02`: Read std::ffi documentation
- `ffi-03`: Implement Drop for wrapped pointers

View File

@@ -1,165 +0,0 @@
---
id: ffi-18
original_id: P.UNS.FFI.18
level: P
impact: HIGH
---
# Avoid Passing Trait Objects to C Interfaces
## Summary
Trait objects (`dyn Trait`) have Rust-specific layout (fat pointers with vtable) that is not compatible with C.
## Rationale
- Trait objects are "fat pointers": data ptr + vtable ptr
- C expects thin pointers (single pointer)
- Vtable layout is not stable across Rust versions
- C cannot call Rust vtable methods
## Bad Example
```rust
// DON'T: Pass trait objects to C
trait Handler {
fn handle(&self, data: i32);
}
extern "C" {
// This won't work - dyn Handler is a fat pointer!
fn set_handler(h: *const dyn Handler);
}
// DON'T: Store trait objects in FFI structs
#[repr(C)]
struct BadCallback {
handler: *const dyn Handler, // Not C-compatible!
}
```
## Good Example
```rust
use std::os::raw::{c_int, c_void};
// DO: Use function pointers with user_data (trampoline pattern)
type HandlerFn = extern "C" fn(data: c_int, user_data: *mut c_void);
extern "C" {
fn set_handler(handler: HandlerFn, user_data: *mut c_void);
}
trait Handler {
fn handle(&self, data: i32);
}
fn register_handler<H: Handler + 'static>(handler: H) {
// Box the handler
let boxed: Box<H> = Box::new(handler);
let user_data = Box::into_raw(boxed) as *mut c_void;
extern "C" fn trampoline<H: Handler>(data: c_int, user_data: *mut c_void) {
let handler = unsafe { &*(user_data as *const H) };
handler.handle(data as i32);
}
unsafe {
set_handler(trampoline::<H>, user_data);
}
}
// DO: Use concrete types when possible
struct ConcreteHandler {
multiplier: i32,
}
impl Handler for ConcreteHandler {
fn handle(&self, data: i32) {
println!("{}", data * self.multiplier);
}
}
// DO: Create C-compatible vtable manually if needed
#[repr(C)]
struct HandlerVtable {
handle: extern "C" fn(this: *const c_void, data: c_int),
drop: extern "C" fn(this: *mut c_void),
}
#[repr(C)]
struct CCompatibleHandler {
data: *mut c_void,
vtable: *const HandlerVtable,
}
impl CCompatibleHandler {
fn new<H: Handler + 'static>(handler: H) -> Self {
extern "C" fn handle_impl<H: Handler>(this: *const c_void, data: c_int) {
let handler = unsafe { &*(this as *const H) };
handler.handle(data as i32);
}
extern "C" fn drop_impl<H: Handler>(this: *mut c_void) {
unsafe { drop(Box::from_raw(this as *mut H)); }
}
static VTABLE: HandlerVtable = HandlerVtable {
handle: handle_impl::<ConcreteHandler>, // Need concrete type
drop: drop_impl::<ConcreteHandler>,
};
Self {
data: Box::into_raw(Box::new(handler)) as *mut c_void,
vtable: &VTABLE,
}
}
fn handle(&self, data: i32) {
unsafe {
((*self.vtable).handle)(self.data, data as c_int);
}
}
}
impl Drop for CCompatibleHandler {
fn drop(&mut self) {
unsafe {
((*self.vtable).drop)(self.data);
}
}
}
```
## Why Trait Objects Don't Work
```
Rust trait object (*const dyn Handler):
[data pointer][vtable pointer] <- 16 bytes on 64-bit
C pointer (void*):
[pointer] <- 8 bytes on 64-bit
The sizes don't match!
```
## Alternatives to Trait Objects
| Instead of | Use |
|------------|-----|
| `dyn Trait` | Function pointer + user_data |
| `Box<dyn Trait>` | Boxed concrete type + trampoline |
| `&dyn Trait` | C-compatible vtable struct |
| `Arc<dyn Trait>` | Reference counting wrapper |
## Checklist
- [ ] Am I passing trait objects across FFI?
- [ ] Can I use concrete types instead?
- [ ] Have I used the trampoline pattern for callbacks?
- [ ] If vtable is needed, is it C-compatible?
## Related Rules
- `ffi-16`: Closure to C with trampoline pattern
- `ffi-14`: Types should have stable layout

View File

@@ -1,71 +0,0 @@
---
id: general-01
original_id: P.UNS.01
level: P
impact: CRITICAL
---
# Do Not Abuse Unsafe to Escape Compiler Safety Checks
## Summary
Unsafe Rust should not be used as an escape hatch from the borrow checker or other compiler safety mechanisms.
## Rationale
The borrow checker exists to prevent memory safety bugs. Using `unsafe` to bypass it defeats Rust's safety guarantees and introduces potential undefined behavior.
## Bad Example
```rust
// DON'T: Using unsafe to bypass borrow checker
fn bad_alias() {
let mut data = vec![1, 2, 3];
let ptr = data.as_mut_ptr();
// Unsafe used to create aliasing mutable references
unsafe {
let ref1 = &mut *ptr;
let ref2 = &mut *ptr; // UB: Two mutable references!
*ref1 = 10;
*ref2 = 20;
}
}
```
## Good Example
```rust
// DO: Work with the borrow checker, not against it
fn good_sequential() {
let mut data = vec![1, 2, 3];
data[0] = 10;
data[0] = 20; // Sequential mutations are fine
}
// DO: Use interior mutability when needed
use std::cell::RefCell;
fn good_interior_mut() {
let data = RefCell::new(vec![1, 2, 3]);
data.borrow_mut()[0] = 10;
}
```
## Legitimate Uses of Unsafe
1. **FFI**: Calling C functions or implementing C-compatible interfaces
2. **Low-level abstractions**: Implementing collections, synchronization primitives
3. **Performance**: Only after profiling shows measurable improvement, and with careful safety analysis
## Checklist
- [ ] Have I tried all safe alternatives first?
- [ ] Is the borrow checker preventing a genuine design need?
- [ ] Can I restructure the code to satisfy the borrow checker?
- [ ] If unsafe is necessary, have I documented the safety invariants?
## Related Rules
- `general-02`: Don't blindly use unsafe for performance
- `safety-02`: Unsafe code authors must verify safety invariants

View File

@@ -1,90 +0,0 @@
---
id: general-02
original_id: P.UNS.02
level: P
impact: CRITICAL
---
# Do Not Blindly Use Unsafe for Performance
## Summary
Do not assume that using `unsafe` will automatically improve performance. Always measure first and verify the safety invariants.
## Rationale
1. Modern Rust optimizers often eliminate bounds checks when they can prove safety
2. Unsafe code may prevent optimizations by breaking aliasing assumptions
3. Unmeasured "optimizations" often provide no real benefit while introducing risk
## Bad Example
```rust
// DON'T: Blind unsafe for "performance"
fn sum_bad(slice: &[i32]) -> i32 {
let mut sum = 0;
// Unnecessary unsafe - LLVM can optimize the safe version
for i in 0..slice.len() {
unsafe {
sum += *slice.get_unchecked(i);
}
}
sum
}
```
## Good Example
```rust
// DO: Use safe iteration - compiler optimizes bounds checks away
fn sum_good(slice: &[i32]) -> i32 {
slice.iter().sum()
}
// DO: If unsafe is justified, document why
fn sum_justified(slice: &[i32]) -> i32 {
let mut sum = 0;
// This is actually slower than iter().sum() in most cases
// Only use get_unchecked when:
// 1. Profiler shows bounds checks as bottleneck
// 2. Iterator patterns can't be used
// 3. Safety is proven by other means
for i in 0..slice.len() {
// SAFETY: i is always < slice.len() due to loop condition
unsafe {
sum += *slice.get_unchecked(i);
}
}
sum
}
```
## When Unsafe Might Be Justified for Performance
1. **Hot inner loops** where profiling shows bounds checks are a bottleneck
2. **SIMD operations** that require specific memory alignment
3. **Lock-free data structures** with carefully verified memory orderings
## Measurement Workflow
```bash
# 1. Benchmark the safe version first
cargo bench --bench my_bench
# 2. Profile to identify actual bottlenecks
cargo flamegraph --bench my_bench
# 3. Only then consider unsafe, with measurements
```
## Checklist
- [ ] Have I benchmarked the safe version?
- [ ] Does profiling show this specific code as a bottleneck?
- [ ] Have I measured the actual improvement from unsafe?
- [ ] Is the performance gain worth the safety risk?
## Related Rules
- `general-01`: Don't abuse unsafe to escape safety checks
- `safety-02`: Unsafe code authors must verify safety invariants

View File

@@ -1,74 +0,0 @@
---
id: general-03
original_id: G.UNS.01
level: G
impact: MEDIUM
---
# Do Not Create Aliases for Types/Methods Named "Unsafe"
## Summary
Do not create type aliases, re-exports, or wrapper methods that hide the "unsafe" nature of operations.
## Rationale
The word "unsafe" in Rust is a signal to developers that extra scrutiny is required. Hiding this signal makes code review harder and can lead to accidental misuse.
## Bad Example
```rust
// DON'T: Hide unsafe behind an alias
type SafePointer = *mut u8; // Still unsafe to dereference!
// DON'T: Wrap unsafe in a "safe-looking" name
pub fn get_value(ptr: *const i32) -> i32 {
unsafe { *ptr } // Caller doesn't know this is unsafe!
}
// DON'T: Re-export unsafe functions with different names
pub use std::mem::transmute as convert;
```
## Good Example
```rust
// DO: Keep "unsafe" visible in the API
pub unsafe fn get_value_unchecked(ptr: *const i32) -> i32 {
*ptr
}
// DO: If providing a safe wrapper, make the safety contract clear
/// Returns the value at the pointer.
///
/// # Safety
/// This is safe because the pointer is validated internally.
pub fn get_value_checked(ptr: *const i32) -> Option<i32> {
if ptr.is_null() {
None
} else {
// SAFETY: We checked for null above
Some(unsafe { *ptr })
}
}
// DO: Use clear naming for raw pointer types
type RawHandle = *mut c_void; // "Raw" signals potential unsafety
```
## Common Violations
1. Creating type aliases that hide pointer types
2. Wrapping unsafe functions in safe-looking functions without proper safety analysis
3. Re-exporting unsafe functions with "friendlier" names
## Checklist
- [ ] Does my API preserve visibility of unsafe operations?
- [ ] If wrapping unsafe code in safe API, is the safety invariant enforced?
- [ ] Are type aliases clearly named to indicate their nature?
## Related Rules
- `safety-06`: Don't expose raw pointers in public APIs
- `safety-09`: Add SAFETY comment before any unsafe block

View File

@@ -1,151 +0,0 @@
---
id: io-01
original_id: P.UNS.FIO.01
level: P
impact: HIGH
---
# Ensure I/O Safety When Using Raw Handles
## Summary
When working with raw file descriptors or handles, ensure they are valid for the duration of use and properly ownership-tracked.
## Rationale
- Raw handles can be closed by other code
- Using a closed handle is undefined behavior
- Handle reuse can cause data corruption
- Rust 1.63+ provides I/O safety traits
## Bad Example
```rust
#[cfg(unix)]
mod bad_example {
use std::os::unix::io::RawFd;
// DON'T: Accept raw handle without ownership
fn bad_read(fd: RawFd) -> std::io::Result<Vec<u8>> {
// What if fd was closed? What if it's reused?
let mut buf = vec![0u8; 1024];
let n = unsafe {
libc::read(fd, buf.as_mut_ptr() as *mut libc::c_void, buf.len())
};
if n < 0 {
Err(std::io::Error::last_os_error())
} else {
buf.truncate(n as usize);
Ok(buf)
}
}
// DON'T: Store raw handle without tracking ownership
struct BadFileRef {
fd: RawFd, // Who owns this? Who closes it?
}
}
```
## Good Example
```rust
#[cfg(unix)]
mod good_example {
use std::os::unix::io::{AsFd, BorrowedFd, OwnedFd, FromRawFd, AsRawFd};
use std::fs::File;
// DO: Use BorrowedFd for borrowed access (Rust 1.63+)
fn good_read(fd: BorrowedFd<'_>) -> std::io::Result<Vec<u8>> {
let mut buf = vec![0u8; 1024];
// BorrowedFd guarantees the fd is valid for this call
let n = unsafe {
libc::read(
fd.as_raw_fd(),
buf.as_mut_ptr() as *mut libc::c_void,
buf.len()
)
};
if n < 0 {
Err(std::io::Error::last_os_error())
} else {
buf.truncate(n as usize);
Ok(buf)
}
}
// DO: Use OwnedFd for owned handles
struct GoodFileOwner {
fd: OwnedFd, // Clearly owns the handle
}
impl Drop for GoodFileOwner {
fn drop(&mut self) {
// OwnedFd closes automatically
}
}
// DO: Use generic AsFd bound for flexibility
fn generic_read<F: AsFd>(f: &F) -> std::io::Result<Vec<u8>> {
good_read(f.as_fd())
}
// Usage
fn example() -> std::io::Result<()> {
let file = File::open("test.txt")?;
// Pass as BorrowedFd
let data = good_read(file.as_fd())?;
// Or use generic function
let data = generic_read(&file)?;
Ok(())
}
// DO: Take ownership from raw fd
fn from_raw(fd: i32) -> Option<GoodFileOwner> {
if fd < 0 {
return None;
}
// SAFETY: Caller guarantees fd is valid and ownership is transferred
let owned = unsafe { OwnedFd::from_raw_fd(fd) };
Some(GoodFileOwner { fd: owned })
}
}
```
## I/O Safety Types (Rust 1.63+)
| Type | Meaning |
|------|---------|
| `OwnedFd` | Owns a file descriptor, closes on drop |
| `BorrowedFd<'a>` | Borrows a fd for lifetime 'a |
| `RawFd` | Raw integer, no safety guarantees |
| `AsFd` | Trait for types that have a fd |
| `From<OwnedFd>` | Create from owned fd |
| `Into<OwnedFd>` | Convert to owned fd |
## Windows Equivalents
```rust
#[cfg(windows)]
use std::os::windows::io::{
OwnedHandle, BorrowedHandle, RawHandle,
AsHandle, FromRawHandle,
OwnedSocket, BorrowedSocket, RawSocket,
AsSocket, FromRawSocket,
};
```
## Checklist
- [ ] Am I using BorrowedFd/OwnedFd instead of RawFd?
- [ ] Is ownership of handles clear?
- [ ] Am I using the AsFd trait for generic code?
- [ ] Is the fd guaranteed valid for the duration of use?
## Related Rules
- `ffi-03`: Implement Drop for resource wrappers
- `safety-02`: Verify safety invariants

View File

@@ -1,130 +0,0 @@
---
id: mem-01
original_id: P.UNS.MEM.01
level: P
impact: HIGH
---
# Choose Appropriate Data Layout for Struct/Tuple/Enum
## Summary
Use `#[repr(...)]` attributes to control data layout when interfacing with C, doing memory mapping, or needing specific guarantees.
## Rationale
Rust's default layout is unspecified and may change between compiler versions. For FFI, persistence, or low-level memory operations, you need predictable layout.
## Repr Attributes
| Attribute | Use Case |
|-----------|----------|
| `#[repr(C)]` | C-compatible layout, stable field order |
| `#[repr(transparent)]` | Single-field struct with same layout as field |
| `#[repr(packed)]` | No padding (alignment = 1), careful with references! |
| `#[repr(align(N))]` | Minimum alignment of N bytes |
| `#[repr(u8)]`, `#[repr(i32)]`, etc. | Enum discriminant type |
## Bad Example
```rust
// DON'T: Assume Rust struct layout matches C
struct BadFFI {
a: u8,
b: u32,
c: u8,
}
// Rust may reorder fields or add different padding than C
// DON'T: Use packed without understanding the risks
#[repr(packed)]
struct Dangerous {
a: u8,
b: u32,
}
fn bad_ref(d: &Dangerous) -> &u32 {
&d.b // UB: Creates unaligned reference!
}
```
## Good Example
```rust
// DO: Use repr(C) for FFI
#[repr(C)]
struct GoodFFI {
a: u8,
b: u32,
c: u8,
}
// Guaranteed: a at 0, padding 1-3, b at 4, c at 8, padding 9-11
// DO: Use repr(transparent) for newtypes
#[repr(transparent)]
struct Wrapper(u32);
// Guaranteed same layout as u32, can be transmuted
// DO: Use repr(packed) carefully, access via copy
#[repr(C, packed)]
struct PackedData {
header: u8,
value: u32,
}
impl PackedData {
fn value(&self) -> u32 {
// Copy out the value to avoid unaligned reference
let ptr = std::ptr::addr_of!(self.value);
// SAFETY: Reading unaligned is OK with read_unaligned
unsafe { ptr.read_unaligned() }
}
}
// DO: Use align for SIMD or cache line alignment
#[repr(C, align(64))]
struct CacheAligned {
data: [u8; 64],
}
// DO: Specify enum discriminant for FFI
#[repr(u8)]
enum Status {
Ok = 0,
Error = 1,
Unknown = 255,
}
```
## Layout Guarantees
```rust
use std::mem::{size_of, align_of};
#[repr(C)]
struct Example {
a: u8, // offset 0, size 1
// padding: 3 bytes
b: u32, // offset 4, size 4
c: u8, // offset 8, size 1
// padding: 3 bytes
}
assert_eq!(size_of::<Example>(), 12);
assert_eq!(align_of::<Example>(), 4);
// repr(Rust) might reorder to: b, a, c -> size 8
```
## Checklist
- [ ] Is this type used in FFI? → Use `#[repr(C)]`
- [ ] Is this a newtype wrapper? → Consider `#[repr(transparent)]`
- [ ] Do I need specific alignment? → Use `#[repr(align(N))]`
- [ ] Am I using packed? → Never create references to packed fields
## Related Rules
- `ffi-13`: Ensure consistent data layout for custom types
- `ffi-14`: Types in FFI should have stable layout
- `ptr-04`: Alignment considerations

View File

@@ -1,113 +0,0 @@
---
id: mem-02
original_id: P.UNS.MEM.02
level: P
impact: CRITICAL
---
# Do Not Modify Memory Variables of Other Processes or Dynamic Libraries
## Summary
Do not directly manipulate memory belonging to other processes or dynamically loaded libraries. Use proper IPC or FFI mechanisms.
## Rationale
- Other processes have separate address spaces; direct access is impossible on modern OSes
- Shared memory requires explicit setup and synchronization
- Dynamic library memory has ownership rules that must be respected
- Violating these causes undefined behavior or security vulnerabilities
## Bad Example
```rust
// DON'T: Try to access another process's memory directly
fn bad_cross_process(ptr: *mut i32) {
// This pointer from another process is meaningless in our address space
unsafe { *ptr = 42; } // Undefined behavior or crash
}
// DON'T: Modify library internals
extern "C" {
static mut LIBRARY_INTERNAL: i32;
}
fn bad_library_access() {
// Modifying library internals breaks encapsulation
unsafe { LIBRARY_INTERNAL = 100; } // May corrupt library state
}
```
## Good Example
```rust
// DO: Use proper IPC for cross-process communication
use std::io::{Read, Write};
use std::os::unix::net::UnixStream;
fn ipc_communication() -> std::io::Result<()> {
let mut stream = UnixStream::connect("/tmp/socket")?;
stream.write_all(b"message")?;
Ok(())
}
// DO: Use shared memory with proper synchronization
#[cfg(unix)]
fn shared_memory_example() {
use std::sync::atomic::{AtomicI32, Ordering};
// Properly set up shared memory region
// let shm = mmap shared memory...
// Use atomic operations for synchronization
let shared: &AtomicI32 = /* ... */;
shared.store(42, Ordering::Release);
}
// DO: Use proper FFI for library interaction
mod ffi {
extern "C" {
pub fn library_set_value(value: i32);
pub fn library_get_value() -> i32;
}
}
fn proper_library_access() {
unsafe {
ffi::library_set_value(42);
let value = ffi::library_get_value();
}
}
// DO: Use Rust's libloading for dynamic libraries
fn dynamic_library() -> Result<(), Box<dyn std::error::Error>> {
let lib = unsafe { libloading::Library::new("mylib.so")? };
let func: libloading::Symbol<extern "C" fn(i32) -> i32> =
unsafe { lib.get(b"my_function")? };
let result = func(42);
Ok(())
}
```
## Memory Ownership Rules
| Memory Type | Owner | Safe Access |
|-------------|-------|-------------|
| Stack variables | Current function | Direct |
| Heap (Box, Vec) | Rust allocator | Through smart pointers |
| Static | Program | With proper synchronization |
| Shared memory | Multiple processes | Atomic ops, mutexes |
| Library memory | Library | Through library API |
| FFI-allocated | C allocator | Through C free functions |
## Checklist
- [ ] Who allocated this memory?
- [ ] Who is responsible for freeing it?
- [ ] Is proper synchronization in place for shared access?
- [ ] Am I using the correct API for cross-boundary access?
## Related Rules
- `mem-03`: Don't let String/Vec drop other process's memory
- `ffi-03`: Implement Drop for wrapped C pointers

View File

@@ -1,127 +0,0 @@
---
id: mem-03
original_id: P.UNS.MEM.03
level: P
impact: CRITICAL
---
# Do Not Let String/Vec Auto-Drop Other Process's Memory
## Summary
Never create `String`, `Vec`, or `Box` from memory allocated outside Rust's allocator. They will try to free the memory with the wrong deallocator.
## Rationale
`String`, `Vec`, and `Box` assume memory was allocated by Rust's global allocator. When dropped, they call `dealloc`. If the memory came from C's `malloc`, a different allocator, or shared memory, this causes undefined behavior.
## Bad Example
```rust
// DON'T: Create String from C-allocated memory
extern "C" {
fn c_get_string() -> *mut std::os::raw::c_char;
}
fn bad_string() -> String {
unsafe {
let ptr = c_get_string();
// BAD: String will try to free with Rust allocator
String::from_raw_parts(ptr as *mut u8, len, cap)
}
}
// DON'T: Create Vec from foreign memory
fn bad_vec(ptr: *mut u8, len: usize) -> Vec<u8> {
// BAD: Vec will free this memory incorrectly
unsafe { Vec::from_raw_parts(ptr, len, len) }
}
// DON'T: Wrap shared memory in Box
fn bad_box(shared_ptr: *mut Data) -> Box<Data> {
// BAD: Box will try to deallocate shared memory!
unsafe { Box::from_raw(shared_ptr) }
}
```
## Good Example
```rust
use std::ffi::CStr;
extern "C" {
fn c_get_string() -> *mut std::os::raw::c_char;
fn c_free_string(s: *mut std::os::raw::c_char);
}
// DO: Copy data into Rust-owned allocation
fn good_string() -> String {
unsafe {
let ptr = c_get_string();
let cstr = CStr::from_ptr(ptr);
let result = cstr.to_string_lossy().into_owned();
c_free_string(ptr); // Free with correct deallocator
result
}
}
// DO: Use wrapper that calls correct deallocator
struct CString {
ptr: *mut std::os::raw::c_char,
}
impl Drop for CString {
fn drop(&mut self) {
unsafe { c_free_string(self.ptr); }
}
}
// DO: Use slice for borrowed view, don't take ownership
fn good_slice(ptr: *const u8, len: usize) -> &'static [u8] {
// Only borrow, don't own
unsafe { std::slice::from_raw_parts(ptr, len) }
}
// DO: For shared memory, use raw pointers or custom wrapper
struct SharedBuffer {
ptr: *mut u8,
len: usize,
}
impl SharedBuffer {
fn as_slice(&self) -> &[u8] {
unsafe { std::slice::from_raw_parts(self.ptr, self.len) }
}
}
impl Drop for SharedBuffer {
fn drop(&mut self) {
// Unmap shared memory, don't deallocate
// munmap(self.ptr, self.len);
}
}
```
## Memory Allocation Compatibility
| Allocator | Can use Rust Vec/String/Box? |
|-----------|------------------------------|
| Rust global allocator | Yes |
| C malloc | No - use wrapper with C free |
| C++ new | No - use wrapper with C++ delete |
| Custom allocator | No - use allocator_api |
| mmap/shared memory | No - use munmap |
| Stack/static | No - never "free" |
## Checklist
- [ ] Who allocated this memory?
- [ ] Is it from Rust's global allocator?
- [ ] If not, do I have a custom Drop that frees correctly?
- [ ] Am I copying data or taking ownership?
## Related Rules
- `mem-02`: Don't modify other process's memory
- `ffi-03`: Implement Drop for wrapped C pointers
- `ffi-07`: Don't implement Drop for types passed to external code

View File

@@ -1,121 +0,0 @@
---
id: mem-04
original_id: P.UNS.MEM.04
level: P
impact: HIGH
---
# Prefer Reentrant Versions of C-API or Syscalls
## Summary
When calling C functions or system calls, use reentrant (`_r`) versions to avoid data races from global state.
## Rationale
Many C library functions use static buffers or global state, making them unsafe in multithreaded programs. Reentrant versions use caller-provided buffers instead.
## Bad Example
```rust
use std::ffi::CStr;
extern "C" {
fn strtok(s: *mut i8, delim: *const i8) -> *mut i8;
fn localtime(time: *const i64) -> *mut Tm;
fn rand() -> i32;
}
// DON'T: Use non-reentrant functions
fn bad_tokenize(s: &mut [i8]) {
unsafe {
let delim = b" \0".as_ptr() as *const i8;
// strtok uses static buffer - not thread-safe!
let token = strtok(s.as_mut_ptr(), delim);
}
}
fn bad_time() {
unsafe {
let now: i64 = 0;
// localtime returns pointer to static buffer
let tm = localtime(&now); // Data race if called from multiple threads!
}
}
fn bad_random() -> i32 {
// rand() uses global state - not thread-safe
unsafe { rand() }
}
```
## Good Example
```rust
extern "C" {
fn strtok_r(s: *mut i8, delim: *const i8, saveptr: *mut *mut i8) -> *mut i8;
fn localtime_r(time: *const i64, result: *mut Tm) -> *mut Tm;
fn rand_r(seed: *mut u32) -> i32;
}
// DO: Use reentrant versions
fn good_tokenize(s: &mut [i8]) {
unsafe {
let delim = b" \0".as_ptr() as *const i8;
let mut saveptr: *mut i8 = std::ptr::null_mut();
// strtok_r uses caller-provided saveptr
let token = strtok_r(s.as_mut_ptr(), delim, &mut saveptr);
}
}
fn good_time() {
unsafe {
let now: i64 = 0;
let mut result: Tm = std::mem::zeroed();
// localtime_r writes to caller-provided buffer
localtime_r(&now, &mut result);
}
}
fn good_random(seed: &mut u32) -> i32 {
// rand_r uses caller-provided seed
unsafe { rand_r(seed) }
}
// BETTER: Use Rust standard library
fn best_time() {
use std::time::SystemTime;
let now = SystemTime::now(); // Thread-safe!
}
fn best_random() -> u32 {
use rand::Rng;
rand::thread_rng().gen() // Thread-safe!
}
```
## Common Non-Reentrant Functions
| Non-Reentrant | Reentrant | Rust Alternative |
|---------------|-----------|------------------|
| `strtok` | `strtok_r` | `str::split` |
| `localtime` | `localtime_r` | `chrono` crate |
| `gmtime` | `gmtime_r` | `chrono` crate |
| `ctime` | `ctime_r` | `chrono` crate |
| `rand` | `rand_r` | `rand` crate |
| `strerror` | `strerror_r` | `std::io::Error` |
| `getenv` | None (inherent race) | `std::env::var` (not atomic) |
| `readdir` | `readdir_r` | `std::fs::read_dir` |
| `gethostbyname` | `getaddrinfo` | `std::net::ToSocketAddrs` |
## Checklist
- [ ] Am I calling a C function that might use global state?
- [ ] Is there a `_r` reentrant version available?
- [ ] Is there a Rust standard library alternative?
- [ ] If neither, do I need synchronization?
## Related Rules
- `ffi-10`: Exported functions must be thread-safe
- `ptr-01`: Don't share raw pointers across threads

View File

@@ -1,147 +0,0 @@
---
id: mem-05
original_id: P.UNS.MEM.05
level: P
impact: MEDIUM
---
# Use Third-Party Crates for Bitfields
## Summary
Use crates like `bitflags`, `bitvec`, or `modular-bitfield` instead of manual bit manipulation for complex bitfield operations.
## Rationale
- Manual bit manipulation is error-prone
- Easy to get offsets, masks, or endianness wrong
- Crates provide type-safe, tested abstractions
- Proc-macro crates generate efficient code
## Bad Example
```rust
// DON'T: Manual bitfield manipulation
struct Flags(u32);
impl Flags {
const READ: u32 = 1 << 0;
const WRITE: u32 = 1 << 1;
const EXECUTE: u32 = 1 << 2;
fn has_read(&self) -> bool {
(self.0 & Self::READ) != 0
}
fn set_read(&mut self) {
self.0 |= Self::READ;
}
fn clear_read(&mut self) {
self.0 &= !Self::READ; // Easy to forget the !
}
}
// DON'T: Manual packed bitfields for FFI
#[repr(C)]
struct PackedHeader {
data: u32,
}
impl PackedHeader {
// Error-prone: wrong shift or mask values
fn version(&self) -> u8 {
((self.data >> 24) & 0xFF) as u8
}
fn flags(&self) -> u16 {
((self.data >> 8) & 0xFFFF) as u16
}
fn tag(&self) -> u8 {
(self.data & 0xFF) as u8
}
}
```
## Good Example
```rust
// DO: Use bitflags for flag sets
use bitflags::bitflags;
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Flags: u32 {
const READ = 1 << 0;
const WRITE = 1 << 1;
const EXECUTE = 1 << 2;
const RW = Self::READ.bits() | Self::WRITE.bits();
}
}
fn use_flags() {
let mut flags = Flags::READ | Flags::WRITE;
flags.insert(Flags::EXECUTE);
flags.remove(Flags::WRITE);
if flags.contains(Flags::READ) {
println!("Readable");
}
}
// DO: Use modular-bitfield for packed structures
use modular_bitfield::prelude::*;
#[bitfield]
#[repr(C)]
struct PackedHeader {
tag: B8, // 8 bits
flags: B16, // 16 bits
version: B8, // 8 bits
}
fn use_packed() {
let header = PackedHeader::new()
.with_version(1)
.with_flags(0x1234)
.with_tag(0xAB);
assert_eq!(header.version(), 1);
assert_eq!(header.flags(), 0x1234);
}
// DO: Use bitvec for arbitrary bit manipulation
use bitvec::prelude::*;
fn use_bitvec() {
let mut bits = bitvec![u8, Msb0; 0; 16];
bits.set(0, true);
bits.set(7, true);
let byte: u8 = bits[0..8].load_be();
assert_eq!(byte, 0b1000_0001);
}
```
## Recommended Crates
| Crate | Use Case | Features |
|-------|----------|----------|
| `bitflags` | Flag sets (like C enums) | Type-safe, const, derives |
| `modular-bitfield` | Packed struct fields | Proc macro, repr(C) |
| `bitvec` | Arbitrary bit arrays | Slicing, iteration |
| `packed_struct` | Binary protocol structs | Endianness, derive |
| `deku` | Binary parsing | Derive, read/write |
## Checklist
- [ ] Am I manipulating multiple bit flags? → Use `bitflags`
- [ ] Am I packing fields into bytes? → Use `modular-bitfield` or `packed_struct`
- [ ] Am I doing binary protocol work? → Consider `deku`
- [ ] Is the manual approach really simpler?
## Related Rules
- `mem-01`: Choose appropriate data layout
- `ffi-13`: Ensure consistent data layout

View File

@@ -1,146 +0,0 @@
---
id: mem-06
original_id: G.UNS.MEM.01
level: G
impact: HIGH
clippy: uninit_assumed_init, uninit_vec
---
# Use MaybeUninit<T> for Uninitialized Memory
## Summary
Use `MaybeUninit<T>` instead of `mem::uninitialized()` or `mem::zeroed()` when working with uninitialized memory.
## Rationale
- `mem::uninitialized()` is deprecated and unsound
- `mem::zeroed()` is UB for types where zero is invalid (references, NonZero, bool)
- `MaybeUninit<T>` clearly marks memory as potentially uninitialized
- Compiler can optimize based on initialization state
## Bad Example
```rust
// DON'T: Use deprecated uninitialized
fn bad_uninit<T>() -> T {
unsafe { std::mem::uninitialized() } // Deprecated, UB
}
// DON'T: Use zeroed for types where zero is invalid
fn bad_zeroed() -> &'static str {
unsafe { std::mem::zeroed() } // UB: null reference
}
fn bad_zeroed_bool() -> bool {
unsafe { std::mem::zeroed() } // UB: 0 might not be valid bool
}
// DON'T: Transmute to "initialize"
fn bad_transmute() -> [String; 10] {
unsafe { std::mem::transmute([0u8; std::mem::size_of::<[String; 10]>()]) }
}
// DON'T: Set Vec length without initializing
fn bad_vec() -> Vec<String> {
let mut v = Vec::with_capacity(10);
unsafe { v.set_len(10); } // Elements are uninitialized!
v
}
```
## Good Example
```rust
use std::mem::MaybeUninit;
// DO: Use MaybeUninit for delayed initialization
fn good_array() -> [String; 10] {
let mut arr: [MaybeUninit<String>; 10] =
unsafe { MaybeUninit::uninit().assume_init() };
for (i, elem) in arr.iter_mut().enumerate() {
elem.write(format!("item {}", i));
}
// SAFETY: All elements initialized above
unsafe { std::mem::transmute::<_, [String; 10]>(arr) }
}
// DO: Use MaybeUninit with arrays (cleaner with array_assume_init)
fn good_array_nightly() -> [String; 10] {
let mut arr: [MaybeUninit<String>; 10] =
[const { MaybeUninit::uninit() }; 10];
for (i, elem) in arr.iter_mut().enumerate() {
elem.write(format!("item {}", i));
}
// On nightly: arr.map(|e| unsafe { e.assume_init() })
unsafe { MaybeUninit::array_assume_init(arr) }
}
// DO: Use zeroed only for types where it's valid
fn good_zeroed() -> [u8; 1024] {
// SAFETY: All-zero bytes is valid for u8
unsafe { std::mem::zeroed() }
}
// DO: Initialize buffer properly
fn good_vec() -> Vec<u8> {
let mut v = Vec::with_capacity(1024);
// Option 1: Resize with default value
v.resize(1024, 0);
// Option 2: Use spare_capacity_mut
let spare = v.spare_capacity_mut();
for elem in spare.iter_mut().take(1024) {
elem.write(0);
}
unsafe { v.set_len(1024); }
v
}
// DO: Use MaybeUninit::uninit_array (nightly) or const array
fn good_uninit_array<const N: usize>() -> [MaybeUninit<u8>; N] {
// Stable: create array of uninit
[const { MaybeUninit::uninit() }; N]
}
```
## MaybeUninit API
```rust
use std::mem::MaybeUninit;
// Creation
let uninit: MaybeUninit<T> = MaybeUninit::uninit();
let zeroed: MaybeUninit<T> = MaybeUninit::zeroed();
let init: MaybeUninit<T> = MaybeUninit::new(value);
// Writing
uninit.write(value); // Returns &mut T
// Reading (unsafe)
let value: T = unsafe { uninit.assume_init() };
let ref_: &T = unsafe { uninit.assume_init_ref() };
let mut_: &mut T = unsafe { uninit.assume_init_mut() };
// Pointer access
let ptr: *const T = uninit.as_ptr();
let mut_ptr: *mut T = uninit.as_mut_ptr();
```
## Checklist
- [ ] Am I using `mem::uninitialized()`? → Replace with `MaybeUninit`
- [ ] Am I using `mem::zeroed()` for non-POD types? → Use `MaybeUninit`
- [ ] Am I setting Vec length without initialization? → Use proper initialization
- [ ] Have I initialized all MaybeUninit before assume_init?
## Related Rules
- `safety-03`: Don't expose uninitialized memory in APIs
- `safety-01`: Panic safety with partial initialization

View File

@@ -1,113 +0,0 @@
---
id: ptr-01
original_id: P.UNS.PTR.01
level: P
impact: CRITICAL
---
# Do Not Share Raw Pointers Across Threads
## Summary
Raw pointers (`*const T`, `*mut T`) are not `Send` or `Sync` by default. Do not share them across threads without ensuring proper synchronization.
## Rationale
Raw pointers have no synchronization guarantees. Sharing them across threads can lead to data races, which are undefined behavior.
## Bad Example
```rust
use std::thread;
// DON'T: Share raw pointers across threads
fn bad_sharing() {
let mut data = 42i32;
let ptr = &mut data as *mut i32;
let handle = thread::spawn(move || {
// This is undefined behavior!
unsafe { *ptr = 100; }
});
// Main thread also accesses - data race!
unsafe { *ptr = 200; }
handle.join().unwrap();
}
// DON'T: Wrap in struct and impl Send unsafely
struct UnsafePtr(*mut i32);
unsafe impl Send for UnsafePtr {} // Unsound without synchronization!
```
## Good Example
```rust
use std::sync::{Arc, Mutex, atomic::{AtomicPtr, Ordering}};
use std::thread;
// DO: Use Arc<Mutex<T>> for shared mutable access
fn good_mutex() {
let data = Arc::new(Mutex::new(42i32));
let data_clone = Arc::clone(&data);
let handle = thread::spawn(move || {
*data_clone.lock().unwrap() = 100;
});
*data.lock().unwrap() = 200;
handle.join().unwrap();
}
// DO: Use AtomicPtr for lock-free pointer sharing
fn good_atomic() {
let data = Box::into_raw(Box::new(42i32));
let atomic_ptr = Arc::new(AtomicPtr::new(data));
let atomic_clone = Arc::clone(&atomic_ptr);
let handle = thread::spawn(move || {
let ptr = atomic_clone.load(Ordering::Acquire);
// SAFETY: We have exclusive access through atomic operations
unsafe { println!("Value: {}", *ptr); }
});
handle.join().unwrap();
// SAFETY: All threads done, we own the memory
unsafe { drop(Box::from_raw(atomic_ptr.load(Ordering::Relaxed))); }
}
// DO: If you must use raw pointers, ensure exclusive access
fn good_exclusive() {
let mut data = vec![1, 2, 3];
// Send data ownership to thread, not pointer
let handle = thread::spawn(move || {
data.push(4);
data
});
let data = handle.join().unwrap();
println!("{:?}", data);
}
```
## When Raw Pointers Across Threads Are Valid
Only with proper synchronization:
- Through `AtomicPtr` with appropriate memory orderings
- Protected by a `Mutex` (don't share the pointer, share the Mutex)
- Using lock-free algorithms with careful memory ordering
## Checklist
- [ ] Does my pointer cross thread boundaries?
- [ ] Is there synchronization preventing concurrent access?
- [ ] Can I use a higher-level abstraction (Arc, Mutex)?
- [ ] If implementing Send/Sync, is thread safety proven?
## Related Rules
- `safety-05`: Consider safety when implementing Send/Sync
- `safety-02`: Verify safety invariants

View File

@@ -1,114 +0,0 @@
---
id: ptr-02
original_id: P.UNS.PTR.02
level: P
impact: MEDIUM
---
# Prefer NonNull<T> Over *mut T
## Summary
Use `NonNull<T>` instead of `*mut T` when the pointer should never be null. This enables null pointer optimization and makes the intent clear.
## Rationale
- `NonNull<T>` guarantees non-null at the type level
- Enables niche optimization: `Option<NonNull<T>>` is the same size as `*mut T`
- Makes invariants explicit in the type system
- Covariant over `T` (like `&T`), which is usually what you want
## Bad Example
```rust
// DON'T: Use *mut when pointer is always non-null
struct MyBox<T> {
ptr: *mut T, // Invariant: never null, but not enforced
}
impl<T> MyBox<T> {
pub fn new(value: T) -> Self {
let ptr = Box::into_raw(Box::new(value));
// ptr is guaranteed non-null, but type doesn't show it
Self { ptr }
}
pub fn get(&self) -> &T {
// Must add null check or document the invariant
unsafe { &*self.ptr }
}
}
```
## Good Example
```rust
use std::ptr::NonNull;
// DO: Use NonNull when pointer is never null
struct MyBox<T> {
ptr: NonNull<T>, // Type guarantees non-null
}
impl<T> MyBox<T> {
pub fn new(value: T) -> Self {
let ptr = Box::into_raw(Box::new(value));
// SAFETY: Box::into_raw never returns null
let ptr = unsafe { NonNull::new_unchecked(ptr) };
Self { ptr }
}
pub fn get(&self) -> &T {
// SAFETY: NonNull guarantees ptr is valid
unsafe { self.ptr.as_ref() }
}
}
impl<T> Drop for MyBox<T> {
fn drop(&mut self) {
// SAFETY: ptr was created from Box::into_raw
unsafe { drop(Box::from_raw(self.ptr.as_ptr())); }
}
}
// DO: Niche optimization with Option
struct OptionalBox<T> {
ptr: Option<NonNull<T>>, // Same size as *mut T!
}
```
## NonNull API
```rust
use std::ptr::NonNull;
// Creating NonNull
let ptr: NonNull<i32> = NonNull::new(raw_ptr).expect("null pointer");
let ptr: NonNull<i32> = unsafe { NonNull::new_unchecked(raw_ptr) };
let ptr: NonNull<i32> = NonNull::dangling(); // For ZSTs or uninitialized
// Using NonNull
let raw: *mut i32 = ptr.as_ptr();
let reference: &i32 = unsafe { ptr.as_ref() };
let mut_ref: &mut i32 = unsafe { ptr.as_mut() };
// Casting
let ptr: NonNull<u8> = ptr.cast::<u8>();
```
## When to Use *mut T Instead
- When null is a valid/expected value
- FFI with C code that may return null
- When variance matters (NonNull is covariant, sometimes you need invariance)
## Checklist
- [ ] Is my pointer ever null? If no, use NonNull
- [ ] Do I need null pointer optimization?
- [ ] Is the variance correct for my use case?
## Related Rules
- `ptr-03`: Use PhantomData for variance and ownership
- `safety-06`: Don't expose raw pointers in public APIs

View File

@@ -1,125 +0,0 @@
---
id: ptr-03
original_id: P.UNS.PTR.03
level: P
impact: HIGH
---
# Use PhantomData<T> for Variance and Ownership with Pointer Generics
## Summary
When a struct contains raw pointers but logically owns or borrows the pointed-to data, use `PhantomData<T>` to tell the compiler about the relationship.
## Rationale
Raw pointers don't carry ownership or lifetime information. `PhantomData` lets you:
- Indicate ownership (for `Drop` check)
- Control variance (covariant, contravariant, invariant)
- Participate in lifetime elision
## Bad Example
```rust
// DON'T: Raw pointer without PhantomData
struct MyVec<T> {
ptr: *mut T,
len: usize,
cap: usize,
}
// Problems:
// 1. Compiler doesn't know we "own" the T values
// 2. T might be incorrectly determined as unused
// 3. Drop check may allow dangling references
```
## Good Example
```rust
use std::marker::PhantomData;
use std::ptr::NonNull;
// DO: Use PhantomData to express ownership
struct MyVec<T> {
ptr: NonNull<T>,
len: usize,
cap: usize,
_marker: PhantomData<T>, // We own T values
}
// For owned data: PhantomData<T>
// For borrowed data: PhantomData<&'a T>
// For mutably borrowed: PhantomData<&'a mut T>
// For function pointers: PhantomData<fn(T)> (contravariant)
// DO: Express lifetime relationships
struct Iter<'a, T> {
ptr: *const T,
end: *const T,
_marker: PhantomData<&'a T>, // Borrows T for 'a
}
impl<'a, T> Iterator for Iter<'a, T> {
type Item = &'a T;
fn next(&mut self) -> Option<Self::Item> {
if self.ptr == self.end {
None
} else {
// SAFETY: ptr < end, so ptr is valid
// Lifetime is tied to 'a through PhantomData
let current = unsafe { &*self.ptr };
self.ptr = unsafe { self.ptr.add(1) };
Some(current)
}
}
}
```
## PhantomData Patterns
| Phantom Type | Meaning | Variance |
|--------------|---------|----------|
| `PhantomData<T>` | Owns T | Covariant |
| `PhantomData<&'a T>` | Borrows T for 'a | Covariant in T, covariant in 'a |
| `PhantomData<&'a mut T>` | Mutably borrows T | Invariant in T, covariant in 'a |
| `PhantomData<*const T>` | Just has pointer | Covariant |
| `PhantomData<*mut T>` | Just has pointer | Invariant |
| `PhantomData<fn(T)>` | Consumes T | Contravariant |
| `PhantomData<fn() -> T>` | Produces T | Covariant |
## Drop Check
```rust
use std::marker::PhantomData;
// This tells the compiler that dropping MyVec may drop T values
struct MyVec<T> {
ptr: NonNull<T>,
_marker: PhantomData<T>,
}
impl<T> Drop for MyVec<T> {
fn drop(&mut self) {
// Drop all T values...
}
}
// Without PhantomData<T>, this might compile incorrectly:
// let x = MyVec::new(&local);
// drop(local); // Would be UB if allowed
// drop(x); // Tries to access dropped local
```
## Checklist
- [ ] Does my pointer type logically own the pointed-to data?
- [ ] Do I need to express a lifetime relationship?
- [ ] What variance do I need for my generic parameter?
- [ ] Will the type be dropped, and does it need drop check?
## Related Rules
- `ptr-02`: Prefer NonNull over *mut T
- `safety-05`: Send/Sync implementation safety

View File

@@ -1,117 +0,0 @@
---
id: ptr-04
original_id: G.UNS.PTR.01
level: G
impact: HIGH
clippy: cast_ptr_alignment
---
# Do Not Dereference Pointers Cast to Misaligned Types
## Summary
When casting a pointer to a different type, ensure the resulting pointer is properly aligned for the target type.
## Rationale
Misaligned pointer dereferences are undefined behavior on most architectures. Even on architectures that support unaligned access, it may cause performance penalties or subtle bugs.
## Bad Example
```rust
// DON'T: Cast without checking alignment
fn bad_cast(bytes: &[u8]) -> u32 {
// BAD: bytes might not be aligned for u32
let ptr = bytes.as_ptr() as *const u32;
unsafe { *ptr } // UB if misaligned!
}
// DON'T: Assume struct layout
#[repr(C)]
struct Header {
flags: u8,
value: u32, // Aligned at offset 4 in the struct
}
fn bad_field_access(bytes: &[u8]) -> u32 {
let header = bytes.as_ptr() as *const Header;
// Even if bytes is 4-byte aligned, this might fail
// if Header has different alignment than expected
unsafe { (*header).value }
}
```
## Good Example
```rust
// DO: Use read_unaligned for potentially misaligned data
fn good_cast(bytes: &[u8]) -> u32 {
assert!(bytes.len() >= 4);
let ptr = bytes.as_ptr() as *const u32;
// SAFETY: We're reading 4 bytes, alignment doesn't matter for read_unaligned
unsafe { ptr.read_unaligned() }
}
// DO: Check alignment before cast
fn good_aligned_cast(bytes: &[u8]) -> Option<&u32> {
if bytes.len() >= 4 && bytes.as_ptr() as usize % std::mem::align_of::<u32>() == 0 {
// SAFETY: Checked length and alignment
Some(unsafe { &*(bytes.as_ptr() as *const u32) })
} else {
None
}
}
// DO: Use from_ne_bytes for portable byte conversion
fn good_from_bytes(bytes: &[u8]) -> u32 {
u32::from_ne_bytes(bytes[..4].try_into().unwrap())
}
// DO: Use bytemuck for safe transmutation
// use bytemuck::{Pod, Zeroable};
// let value: u32 = bytemuck::pod_read_unaligned(bytes);
// DO: Use align_to for splitting at alignment boundaries
fn process_aligned(bytes: &[u8]) {
let (prefix, aligned, suffix) = unsafe { bytes.align_to::<u32>() };
// prefix and suffix are unaligned portions
// aligned is a &[u32] that's properly aligned
}
```
## Alignment Check Helpers
```rust
fn is_aligned<T>(ptr: *const u8) -> bool {
ptr as usize % std::mem::align_of::<T>() == 0
}
/// Align a pointer up to the next aligned address
fn align_up<T>(ptr: *const u8) -> *const u8 {
let align = std::mem::align_of::<T>();
let addr = ptr as usize;
let aligned = (addr + align - 1) & !(align - 1);
aligned as *const u8
}
```
## Architecture Notes
| Arch | Misaligned Access |
|------|-------------------|
| x86/x64 | Works but slower |
| ARM | UB, may trap or give wrong results |
| RISC-V | UB, may trap |
| WASM | UB |
## Checklist
- [ ] Is my pointer cast changing alignment requirements?
- [ ] Is the source pointer guaranteed to be aligned?
- [ ] Should I use read_unaligned instead?
- [ ] Can I use safe conversion methods (from_ne_bytes)?
## Related Rules
- `mem-01`: Choose appropriate data layout
- `ffi-13`: Ensure consistent data layout

View File

@@ -1,120 +0,0 @@
---
id: ptr-05
original_id: G.UNS.PTR.02
level: G
impact: CRITICAL
clippy: cast_ref_to_mut
---
# Do Not Manually Convert Immutable Pointer to Mutable
## Summary
Never cast `*const T` to `*mut T` and dereference it to write. This violates aliasing rules and is undefined behavior.
## Rationale
Creating `*const T` from `&T` implies immutability. Other references might exist. Writing through a `*mut T` created from `*const T` creates mutable aliasing, which is UB.
## Bad Example
```rust
// DON'T: Cast *const to *mut
fn bad_mutate(value: &i32) {
let ptr = value as *const i32 as *mut i32;
unsafe { *ptr = 42; } // UB: Mutating through &
}
// DON'T: Use transmute to convert
fn bad_transmute(value: &i32) -> &mut i32 {
unsafe { std::mem::transmute(value) } // UB!
}
// DON'T: "I know this is the only reference"
fn bad_claim(value: &i32) {
// Even if you "know" there's only one reference,
// the compiler assumes & means no mutation
let ptr = value as *const i32 as *mut i32;
unsafe { *ptr += 1; } // Still UB - compiler may optimize incorrectly
}
```
## Good Example
```rust
// DO: Take &mut if you need to mutate
fn good_mutate(value: &mut i32) {
*value = 42;
}
// DO: Use interior mutability
use std::cell::{Cell, RefCell, UnsafeCell};
struct Mutable {
value: Cell<i32>, // Interior mutability
}
impl Mutable {
fn modify(&self) {
self.value.set(42); // OK: Cell provides interior mutability
}
}
// DO: Use UnsafeCell if you need raw unsafe interior mutability
struct RawMutable {
value: UnsafeCell<i32>,
}
impl RawMutable {
fn modify(&self) {
// SAFETY: We ensure exclusive access through external means
unsafe { *self.value.get() = 42; }
}
}
```
## The UnsafeCell Exception
`UnsafeCell<T>` is the ONLY valid way to get `*mut T` from `&self`:
```rust
use std::cell::UnsafeCell;
pub struct MyMutex<T> {
data: UnsafeCell<T>,
// ... lock state
}
impl<T> MyMutex<T> {
pub fn lock(&self) -> Guard<'_, T> {
// acquire lock...
// SAFETY: UnsafeCell allows this, lock ensures exclusivity
Guard { data: unsafe { &mut *self.data.get() } }
}
}
```
## Why This Is Always UB
The compiler assumes:
1. `&T` means no mutation will occur
2. Multiple `&T` can exist simultaneously
3. Optimizations can be made based on these assumptions
When you mutate through cast pointer:
1. Other `&T` references see inconsistent values
2. Compiler may cache/eliminate reads
3. Results are unpredictable
## Checklist
- [ ] Am I trying to mutate through `&`?
- [ ] Should I use `&mut` instead?
- [ ] Should I use `Cell`, `RefCell`, or `UnsafeCell`?
- [ ] Is the original type designed for interior mutability?
## Related Rules
- `safety-08`: Mutable return from immutable parameter is wrong
- `safety-02`: Verify safety invariants

View File

@@ -1,110 +0,0 @@
---
id: ptr-06
original_id: G.UNS.PTR.03
level: G
impact: LOW
clippy: ptr_as_ptr
---
# Prefer pointer::cast Over `as` for Pointer Casting
## Summary
Use the `cast()` method instead of `as` for pointer type conversions. It's clearer and prevents accidental provenance loss.
## Rationale
- `cast()` only changes the pointed-to type, not pointer properties
- `as` can accidentally convert to integer and back, losing provenance
- `cast()` is more explicit about intent
- Better tooling support (clippy, miri)
## Bad Example
```rust
// DON'T: Use `as` for pointer casts
fn bad_cast(ptr: *const u8) -> *const i32 {
ptr as *const i32 // Works, but less clear
}
// DON'T: Accidental provenance loss
fn bad_roundtrip(ptr: *const u8) -> *const u8 {
let addr = ptr as usize; // Converts to integer
addr as *const u8 // Loses provenance information!
}
// DON'T: Multiple `as` casts in chain
fn bad_chain(ptr: *const u8) -> *mut i32 {
ptr as *mut u8 as *mut i32 // Hard to follow
}
```
## Good Example
```rust
// DO: Use cast() for pointer type changes
fn good_cast(ptr: *const u8) -> *const i32 {
ptr.cast::<i32>()
}
// DO: Use cast_mut() for const-to-mut (when valid)
fn good_cast_mut(ptr: *const u8) -> *mut u8 {
ptr.cast_mut() // Only use when mutation is valid!
}
// DO: Use cast_const() for mut-to-const
fn good_cast_const(ptr: *mut u8) -> *const u8 {
ptr.cast_const()
}
// DO: Chain casts clearly
fn good_chain(ptr: *const u8) -> *mut i32 {
ptr.cast_mut().cast::<i32>()
}
// DO: Use with_addr() for address manipulation (nightly)
#[cfg(feature = "strict_provenance")]
fn good_provenance(ptr: *const u8, new_addr: usize) -> *const u8 {
ptr.with_addr(new_addr) // Preserves provenance
}
```
## Pointer Method Reference
| Method | From | To | Notes |
|--------|------|-----|-------|
| `.cast::<U>()` | `*T` | `*U` | Changes pointee type |
| `.cast_mut()` | `*const T` | `*mut T` | Removes const |
| `.cast_const()` | `*mut T` | `*const T` | Adds const |
| `.addr()` | `*T` | `usize` | Gets address (nightly) |
| `.with_addr(usize)` | `*T` | `*T` | Changes address, keeps provenance |
| `.map_addr(fn)` | `*T` | `*T` | Transforms address |
## Provenance Considerations
```rust
// Provenance = permission to access memory
// BAD: Loses provenance
let ptr: *const u8 = &data as *const u8;
let addr = ptr as usize;
let ptr2 = addr as *const u8; // ptr2 has no provenance!
// GOOD: Preserves provenance (nightly strict_provenance)
let ptr2 = ptr.with_addr(addr); // Still has permission
// GOOD: Use expose/from_exposed when provenance must cross integer
let addr = ptr.expose_addr(); // "Expose" the provenance
let ptr2 = std::ptr::from_exposed_addr(addr); // Recover it
```
## Checklist
- [ ] Am I using `as` where `cast()` would be clearer?
- [ ] Am I accidentally converting through `usize`?
- [ ] Do I need to preserve provenance?
## Related Rules
- `ptr-04`: Alignment considerations when casting
- `ptr-05`: Don't convert const to mut improperly

View File

@@ -1,113 +0,0 @@
---
id: safety-01
original_id: P.UNS.SAS.01
level: P
impact: CRITICAL
clippy: panic_in_result_fn
---
# Be Aware of Memory Safety Issues from Panics
## Summary
Panics in unsafe code can leave data structures in an inconsistent state, leading to undefined behavior when the panic is caught.
## Rationale
When a panic occurs, Rust unwinds the stack and runs destructors. If unsafe code has partially modified data, the destructors may observe invalid state.
## Bad Example
```rust
// DON'T: Panic can leave Vec in invalid state
impl<T> MyVec<T> {
pub fn push(&mut self, value: T) {
if self.len == self.cap {
self.grow(); // Might panic during allocation
}
unsafe {
// If Clone::clone() panics after incrementing len,
// drop will try to drop uninitialized memory
self.len += 1;
ptr::write(self.ptr.add(self.len - 1), value.clone());
}
}
}
```
## Good Example
```rust
// DO: Ensure panic safety by ordering operations correctly
impl<T> MyVec<T> {
pub fn push(&mut self, value: T) {
if self.len == self.cap {
self.grow();
}
unsafe {
// Write first, then increment len
// If write somehow panics, len is still valid
ptr::write(self.ptr.add(self.len), value);
self.len += 1; // Only increment after successful write
}
}
}
// DO: Use guards for complex operations
impl<T: Clone> MyVec<T> {
pub fn extend_from_slice(&mut self, slice: &[T]) {
self.reserve(slice.len());
let mut guard = PanicGuard {
vec: self,
initialized: 0,
};
for item in slice {
unsafe {
ptr::write(guard.vec.ptr.add(guard.vec.len + guard.initialized), item.clone());
guard.initialized += 1;
}
}
// Success - update len and forget guard
self.len += guard.initialized;
std::mem::forget(guard);
}
}
struct PanicGuard<'a, T> {
vec: &'a mut MyVec<T>,
initialized: usize,
}
impl<T> Drop for PanicGuard<'_, T> {
fn drop(&mut self) {
// Clean up partially initialized elements on panic
unsafe {
for i in 0..self.initialized {
ptr::drop_in_place(self.vec.ptr.add(self.vec.len + i));
}
}
}
}
```
## Key Patterns
1. **Update bookkeeping after operations**: Increment length only after writing
2. **Use panic guards**: RAII types that clean up on panic
3. **Order operations carefully**: Ensure invariants hold if panic occurs at any point
## Checklist
- [ ] What happens if this code panics at each line?
- [ ] Are all invariants maintained if we unwind from here?
- [ ] Do I need a panic guard for cleanup?
## Related Rules
- `safety-04`: Avoid double-free from panic safety issues
- `safety-02`: Verify safety invariants

View File

@@ -1,90 +0,0 @@
---
id: safety-02
original_id: P.UNS.SAS.02
level: P
impact: CRITICAL
---
# Unsafe Code Authors Must Verify Safety Invariants
## Summary
When writing unsafe code, you are taking responsibility for upholding all safety invariants that the compiler normally enforces.
## Rationale
Unsafe blocks don't disable safety requirements - they transfer responsibility from the compiler to the programmer. You must manually verify what the compiler normally checks.
## Safety Invariants to Verify
1. **Pointer validity**: Non-null, aligned, points to valid memory
2. **Aliasing**: No mutable aliasing (two &mut to same memory)
3. **Initialization**: Memory is initialized before read
4. **Lifetime**: References don't outlive their referents
5. **Type validity**: Data matches the expected type's invariants
6. **Thread safety**: Proper synchronization for concurrent access
## Bad Example
```rust
// DON'T: Blindly trust inputs
unsafe fn process(ptr: *const Data, len: usize) {
for i in 0..len {
// No verification that ptr is valid or len is correct!
let item = &*ptr.add(i);
process_item(item);
}
}
```
## Good Example
```rust
// DO: Document and verify invariants
/// Processes a slice of Data items.
///
/// # Safety
///
/// - `ptr` must be non-null and aligned for `Data`
/// - `ptr` must point to `len` consecutive initialized `Data` items
/// - The memory must not be mutated during this call
/// - `len * size_of::<Data>()` must not overflow `isize::MAX`
unsafe fn process(ptr: *const Data, len: usize) {
debug_assert!(!ptr.is_null(), "ptr must not be null");
debug_assert!(ptr.is_aligned(), "ptr must be aligned");
for i in 0..len {
// SAFETY: Caller guarantees ptr points to len valid items
let item = &*ptr.add(i);
process_item(item);
}
}
// DO: Provide safe wrapper when possible
fn process_slice(data: &[Data]) {
// SAFETY: slice guarantees all invariants
unsafe { process(data.as_ptr(), data.len()) }
}
```
## Invariant Documentation Template
```rust
/// # Safety
///
/// The caller must ensure that:
/// - [List each invariant]
/// - [Explain why each matters]
```
## Checklist
- [ ] Have I listed all safety invariants?
- [ ] Can I prove each invariant holds at the call site?
- [ ] Have I added debug assertions where possible?
- [ ] Have I documented invariants in /// # Safety section?
## Related Rules
- `safety-09`: Add SAFETY comment before any unsafe block
- `safety-10`: Add Safety section in docs for public unsafe functions

View File

@@ -1,121 +0,0 @@
---
id: safety-03
original_id: P.UNS.SAS.03
level: P
impact: CRITICAL
clippy: uninit_assumed_init
---
# Do Not Expose Uninitialized Memory in Public APIs
## Summary
Public APIs must never return or expose uninitialized memory to callers.
## Rationale
Reading uninitialized memory is undefined behavior in Rust. Safe code should never be able to access uninitialized memory through your API.
## Bad Example
```rust
// DON'T: Expose uninitialized memory
pub struct Buffer {
data: [u8; 1024],
len: usize,
}
impl Buffer {
pub fn new() -> Self {
// BAD: data is uninitialized
unsafe {
Self {
data: std::mem::MaybeUninit::uninit().assume_init(),
len: 0,
}
}
}
// BAD: Returns reference to potentially uninitialized data
pub fn as_slice(&self) -> &[u8] {
&self.data[..self.len] // What if len > initialized portion?
}
}
```
## Good Example
```rust
use std::mem::MaybeUninit;
// DO: Use MaybeUninit properly and only expose initialized data
pub struct Buffer {
data: Box<[MaybeUninit<u8>; 1024]>,
len: usize, // Invariant: data[0..len] is initialized
}
impl Buffer {
pub fn new() -> Self {
Self {
// MaybeUninit doesn't require initialization
data: Box::new([MaybeUninit::uninit(); 1024]),
len: 0,
}
}
pub fn push(&mut self, byte: u8) {
if self.len < 1024 {
self.data[self.len].write(byte);
self.len += 1;
}
}
// Only returns initialized portion
pub fn as_slice(&self) -> &[u8] {
// SAFETY: self.len bytes are initialized (invariant)
unsafe {
std::slice::from_raw_parts(
self.data.as_ptr() as *const u8,
self.len
)
}
}
}
impl Drop for Buffer {
fn drop(&mut self) {
// Only drop initialized elements
// For u8 this is a no-op, but important for Drop types
}
}
```
## Patterns for Uninitialized Memory
```rust
// Pattern 1: MaybeUninit for delayed initialization
let mut value: MaybeUninit<ExpensiveType> = MaybeUninit::uninit();
initialize_expensive(&mut value);
let value = unsafe { value.assume_init() };
// Pattern 2: Vec::with_capacity for growable buffers
let mut vec = Vec::with_capacity(100);
// vec.len() is 0, capacity is 100
// No uninitialized memory is accessible
// Pattern 3: Box::new_uninit (nightly)
let mut boxed = Box::<[u8; 1024]>::new_uninit();
boxed.write([0u8; 1024]);
let boxed = unsafe { boxed.assume_init() };
```
## Checklist
- [ ] Does my API ever return references to uninitialized memory?
- [ ] Are length/capacity invariants properly maintained?
- [ ] Is MaybeUninit used instead of transmute for uninitialized data?
## Related Rules
- `mem-06`: Use MaybeUninit<T> for uninitialized memory
- `safety-01`: Panic safety with partial initialization

View File

@@ -1,109 +0,0 @@
---
id: safety-04
original_id: P.UNS.SAS.04
level: P
impact: CRITICAL
---
# Avoid Double-Free from Panic Safety Issues
## Summary
Ensure that resources are not freed twice, especially when panics can occur during operations.
## Rationale
Double-free is undefined behavior. Panics during unsafe operations can cause destructors to run on already-freed or partially-constructed data.
## Bad Example
```rust
// DON'T: Potential double-free on panic
impl<T> MyVec<T> {
pub fn pop(&mut self) -> Option<T> {
if self.len == 0 {
None
} else {
self.len -= 1;
unsafe {
// If something panics after this read but before return,
// Drop will try to drop this element again
Some(ptr::read(self.ptr.add(self.len)))
}
}
}
}
// DON'T: Double-free with ManuallyDrop misuse
fn bad_swap<T>(a: &mut T, b: &mut T) {
unsafe {
let tmp = ptr::read(a);
ptr::write(a, ptr::read(b)); // If this panics, tmp leaks
ptr::write(b, tmp);
}
}
```
## Good Example
```rust
// DO: Use std::mem::take or swap
fn good_swap<T: Default>(a: &mut T, b: &mut T) {
std::mem::swap(a, b); // Safe and correct
}
// DO: Use ManuallyDrop for panic safety
use std::mem::ManuallyDrop;
impl<T> MyVec<T> {
pub fn pop(&mut self) -> Option<T> {
if self.len == 0 {
None
} else {
self.len -= 1; // Decrement first
unsafe {
// SAFETY: len was decremented, so this slot won't be
// dropped again by Vec's Drop impl
Some(ptr::read(self.ptr.add(self.len)))
}
}
}
}
// DO: Use scopeguard or manual cleanup
fn safe_operation<T: Clone>(data: &mut [T], source: &[T]) {
// Track what we've written for cleanup on panic
let mut written = 0;
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
for (i, item) in source.iter().enumerate() {
data[i] = item.clone();
written = i + 1;
}
}));
if result.is_err() {
// Clean up on panic (if T needs special handling)
// In this case, safe code handles it automatically
}
}
```
## Patterns to Avoid Double-Free
1. **Decrement length before reading**: Vec's Drop won't touch the read element
2. **Use ManuallyDrop**: Explicitly control when Drop runs
3. **Use std::mem::replace/swap**: Safe alternatives for move semantics
4. **Panic guards**: RAII cleanup on unwind
## Checklist
- [ ] After reading memory, is it marked as "moved"?
- [ ] Will Drop run on this memory? Should it?
- [ ] What happens if this code panics at each point?
- [ ] Are length/count bookkeeping updates ordered correctly?
## Related Rules
- `safety-01`: Panic safety in unsafe code
- `ptr-01`: Don't share raw pointers across threads

View File

@@ -1,113 +0,0 @@
---
id: safety-05
original_id: P.UNS.SAS.05
level: P
impact: CRITICAL
clippy: non_send_fields_in_send_ty
---
# Consider Safety When Manually Implementing Auto Traits
## Summary
When manually implementing `Send` or `Sync`, you must ensure thread safety invariants are upheld.
## Rationale
`Send` and `Sync` are unsafe traits because incorrect implementations cause data races, which are undefined behavior. The compiler auto-implements them conservatively, but manual implementations require careful analysis.
## Trait Meanings
- **`Send`**: Safe to transfer ownership to another thread
- **`Sync`**: Safe to share references (`&T`) between threads (i.e., `&T: Send`)
## Bad Example
```rust
// DON'T: Unsafe Send/Sync without thread safety
struct NotThreadSafe {
ptr: *mut i32, // Raw pointers are not Send/Sync
}
// BAD: This is unsound!
unsafe impl Send for NotThreadSafe {}
unsafe impl Sync for NotThreadSafe {}
// DON'T: Rc-like type with unsafe Sync
struct MyRc<T> {
ptr: *mut RcInner<T>,
}
struct RcInner<T> {
count: usize, // Not atomic!
data: T,
}
// BAD: count is not atomic, concurrent access is UB
unsafe impl<T: Send> Sync for MyRc<T> {}
```
## Good Example
```rust
use std::sync::atomic::{AtomicUsize, Ordering};
use std::ptr::NonNull;
// DO: Use atomic operations for thread-safe reference counting
struct MyArc<T> {
ptr: NonNull<ArcInner<T>>,
}
struct ArcInner<T> {
count: AtomicUsize, // Atomic for thread safety
data: T,
}
// SAFETY: The data is behind atomic reference counting,
// and T: Send + Sync ensures the data itself is thread-safe
unsafe impl<T: Send + Sync> Send for MyArc<T> {}
unsafe impl<T: Send + Sync> Sync for MyArc<T> {}
// DO: Document why it's safe
/// A thread-safe wrapper around a raw file descriptor.
///
/// # Safety
///
/// The file descriptor is valid for the lifetime of this struct,
/// and file descriptors are safe to use from any thread.
struct ThreadSafeFd {
fd: std::os::unix::io::RawFd,
}
// SAFETY: File descriptors are just integers and can be used
// from any thread. The actual I/O operations are thread-safe
// at the OS level.
unsafe impl Send for ThreadSafeFd {}
unsafe impl Sync for ThreadSafeFd {}
```
## Decision Tree
```
Does your type contain:
- Raw pointers? → Probably not auto Send/Sync
- Rc/RefCell? → Not Sync (Rc not Send either)
- Cell/UnsafeCell? → Not Sync
- Interior mutability? → Needs synchronization for Sync
To manually implement:
- Send: Can another thread safely drop this?
- Sync: Can multiple threads safely call &self methods?
```
## Checklist
- [ ] Does my type contain any non-Send/Sync fields?
- [ ] Is interior mutability properly synchronized (Mutex, atomic)?
- [ ] Would concurrent access cause data races?
- [ ] Have I documented why the implementation is safe?
## Related Rules
- `ptr-01`: Don't share raw pointers across threads
- `safety-02`: Verify safety invariants

View File

@@ -1,137 +0,0 @@
---
id: safety-06
original_id: P.UNS.SAS.06
level: P
impact: HIGH
---
# Do Not Expose Raw Pointers in Public APIs
## Summary
Public APIs should use safe abstractions (references, slices, smart pointers) instead of exposing raw pointers.
## Rationale
Raw pointers bypass Rust's safety guarantees. Exposing them in public APIs forces users into unsafe code and makes it easy to create undefined behavior.
## Bad Example
```rust
// DON'T: Expose raw pointers in public API
pub struct Buffer {
data: *mut u8,
len: usize,
}
impl Buffer {
// BAD: Returns raw pointer
pub fn as_ptr(&self) -> *const u8 {
self.data
}
// BAD: Takes raw pointer as input
pub fn from_ptr(ptr: *mut u8, len: usize) -> Self {
Self { data: ptr, len }
}
// BAD: Exposes internal pointer mutably
pub fn as_mut_ptr(&mut self) -> *mut u8 {
self.data
}
}
```
## Good Example
```rust
// DO: Use safe abstractions
pub struct Buffer {
data: Vec<u8>,
}
impl Buffer {
// Returns a safe reference
pub fn as_slice(&self) -> &[u8] {
&self.data
}
// Takes safe input
pub fn from_slice(data: &[u8]) -> Self {
Self { data: data.to_vec() }
}
// Mutable access through safe reference
pub fn as_mut_slice(&mut self) -> &mut [u8] {
&mut self.data
}
}
// DO: If raw pointers are needed, provide unsafe API with documentation
impl Buffer {
/// Returns a pointer to the buffer's data.
///
/// # Safety
///
/// The pointer is valid for `self.len()` bytes and must not be
/// used after the Buffer is dropped or reallocated.
pub fn as_ptr(&self) -> *const u8 {
self.data.as_ptr()
}
/// Creates a Buffer from a raw pointer.
///
/// # Safety
///
/// - `ptr` must point to `len` valid bytes
/// - The memory must be allocated with the global allocator
/// - Caller transfers ownership of the memory to Buffer
pub unsafe fn from_raw_parts(ptr: *mut u8, len: usize, cap: usize) -> Self {
Self {
data: Vec::from_raw_parts(ptr, len, cap)
}
}
}
```
## Patterns for Safe Pointer APIs
```rust
// Pattern 1: Use NonNull for internal pointers
use std::ptr::NonNull;
pub struct MyBox<T> {
ptr: NonNull<T>, // Internal use only
}
impl<T> MyBox<T> {
// Safe public API
pub fn get(&self) -> &T {
// SAFETY: ptr is always valid while MyBox exists
unsafe { self.ptr.as_ref() }
}
}
// Pattern 2: Callback-based access
impl Buffer {
// User can work with pointer in controlled context
pub fn with_ptr<F, R>(&self, f: F) -> R
where
F: FnOnce(*const u8, usize) -> R,
{
f(self.data.as_ptr(), self.data.len())
}
}
```
## Checklist
- [ ] Can this API use references instead of pointers?
- [ ] Can this API use slices instead of pointer + length?
- [ ] If pointers are necessary, is the API marked `unsafe`?
- [ ] Are safety requirements documented?
## Related Rules
- `general-03`: Don't create aliases for unsafe items
- `safety-10`: Document safety requirements for public unsafe functions

View File

@@ -1,107 +0,0 @@
---
id: safety-07
original_id: P.UNS.SAS.07
level: P
impact: MEDIUM
---
# Provide Unsafe Counterparts for Performance Alongside Safe Methods
## Summary
When providing performance-critical operations that skip safety checks, offer both a safe checked version and an unsafe unchecked version.
## Rationale
Users who need maximum performance can opt into unsafe, while others get safety by default. This follows the "safe by default, unsafe opt-in" principle.
## Bad Example
```rust
// DON'T: Only provide unsafe version
impl<T> MySlice<T> {
/// Gets an element by index.
///
/// # Safety
/// Index must be in bounds.
pub unsafe fn get(&self, index: usize) -> &T {
&*self.ptr.add(index)
}
}
// DON'T: Only provide checked version when performance matters
impl<T> MySlice<T> {
pub fn get(&self, index: usize) -> Option<&T> {
if index < self.len {
Some(unsafe { &*self.ptr.add(index) })
} else {
None
}
}
// Missing: get_unchecked for performance-critical code
}
```
## Good Example
```rust
// DO: Provide both versions
impl<T> MySlice<T> {
/// Gets an element by index, returning `None` if out of bounds.
#[inline]
pub fn get(&self, index: usize) -> Option<&T> {
if index < self.len {
// SAFETY: We just verified index < len
Some(unsafe { self.get_unchecked(index) })
} else {
None
}
}
/// Gets an element by index without bounds checking.
///
/// # Safety
///
/// Calling this method with an out-of-bounds index is undefined behavior.
#[inline]
pub unsafe fn get_unchecked(&self, index: usize) -> &T {
debug_assert!(index < self.len, "index out of bounds");
&*self.ptr.add(index)
}
/// Gets an element, panicking if out of bounds.
#[inline]
pub fn get_or_panic(&self, index: usize) -> &T {
assert!(index < self.len, "index {} out of bounds for len {}", index, self.len);
// SAFETY: We just asserted index < len
unsafe { self.get_unchecked(index) }
}
}
```
## Standard Library Patterns
| Safe Method | Unsafe Counterpart |
|-------------|-------------------|
| `slice.get(i)` | `slice.get_unchecked(i)` |
| `str.chars().nth(i)` | `str.get_unchecked(range)` |
| `vec.pop()` | `vec.set_len()` + `ptr::read` |
| `String::from_utf8()` | `String::from_utf8_unchecked()` |
## Naming Conventions
- Safe: `method_name()`
- Unsafe: `method_name_unchecked()`
- Or: `get()` vs `get_unchecked()`
## Checklist
- [ ] Does my safe method have an unsafe counterpart for hot paths?
- [ ] Does my unsafe method have a safe alternative for normal use?
- [ ] Are both methods documented with their trade-offs?
- [ ] Does the unsafe version include debug assertions?
## Related Rules
- `general-02`: Don't blindly use unsafe for performance
- `safety-09`: Add SAFETY comments

View File

@@ -1,124 +0,0 @@
---
id: safety-08
original_id: P.UNS.SAS.08
level: P
impact: CRITICAL
clippy: mut_from_ref
---
# Mutable Return from Immutable Parameter is Wrong
## Summary
A function taking `&self` or `&T` must not return `&mut T` to the same data without interior mutability.
## Rationale
Returning `&mut` from `&` violates Rust's aliasing rules. The caller has an immutable borrow, so they can create additional `&` references. Returning `&mut` creates mutable aliasing, which is undefined behavior.
## Bad Example
```rust
// DON'T: Return &mut from &self
struct Container {
data: i32,
}
impl Container {
// WRONG: This is undefined behavior!
pub fn get_mut(&self) -> &mut i32 {
unsafe {
// Creating &mut from & is ALWAYS wrong
&mut *(&self.data as *const i32 as *mut i32)
}
}
}
// DON'T: Transmute & to &mut
fn bad_transmute<T>(reference: &T) -> &mut T {
unsafe { std::mem::transmute(reference) } // UB!
}
```
## Good Example
```rust
use std::cell::{Cell, RefCell, UnsafeCell};
// DO: Use interior mutability types
struct Container {
data: Cell<i32>, // For Copy types
complex: RefCell<String>, // For non-Copy with runtime checks
}
impl Container {
pub fn get(&self) -> i32 {
self.data.get()
}
pub fn set(&self, value: i32) {
self.data.set(value);
}
pub fn modify_complex(&self, f: impl FnOnce(&mut String)) {
f(&mut self.complex.borrow_mut());
}
}
// DO: Use UnsafeCell for custom interior mutability
struct MyMutex<T> {
locked: std::sync::atomic::AtomicBool,
data: UnsafeCell<T>,
}
impl<T> MyMutex<T> {
pub fn lock(&self) -> MutexGuard<'_, T> {
// Acquire lock...
MutexGuard { mutex: self }
}
}
struct MutexGuard<'a, T> {
mutex: &'a MyMutex<T>,
}
impl<T> std::ops::DerefMut for MutexGuard<'_, T> {
fn deref_mut(&mut self) -> &mut T {
// SAFETY: We hold the lock, so exclusive access is guaranteed
unsafe { &mut *self.mutex.data.get() }
}
}
```
## The Only Valid Pattern
The ONLY way to get `&mut` from `&` is through `UnsafeCell`:
```rust
use std::cell::UnsafeCell;
struct ValidInteriorMut {
data: UnsafeCell<i32>,
}
impl ValidInteriorMut {
// This is sound ONLY because UnsafeCell opts out of aliasing rules
// AND we guarantee exclusive access (e.g., through a lock)
pub fn get_mut(&self) -> &mut i32 {
// Must ensure no other references exist!
unsafe { &mut *self.data.get() }
}
}
```
## Checklist
- [ ] Am I trying to return &mut from a & method?
- [ ] If yes, am I using UnsafeCell or a type built on it?
- [ ] Am I guaranteeing exclusive access before creating &mut?
- [ ] Would Cell, RefCell, or Mutex solve my problem safely?
## Related Rules
- `ptr-05`: Don't manually convert *const to *mut
- `safety-02`: Verify safety invariants

View File

@@ -1,122 +0,0 @@
---
id: safety-09
original_id: P.UNS.SAS.09
level: P
impact: CRITICAL
clippy: undocumented_unsafe_blocks
---
# Add SAFETY Comment Before Any Unsafe Block
## Summary
Every `unsafe` block or `unsafe impl` must have a `// SAFETY:` comment explaining why the operation is safe.
## Rationale
SAFETY comments force the author to think about invariants and help reviewers verify correctness. They serve as documentation for future maintainers.
## Bad Example
```rust
// DON'T: Unsafe without explanation
fn get_unchecked(slice: &[i32], index: usize) -> i32 {
unsafe { *slice.get_unchecked(index) }
}
// DON'T: Vague or unhelpful comments
fn bad_comments(ptr: *const i32) -> i32 {
// This is unsafe
unsafe { *ptr }
// Trust me
unsafe { *ptr }
// Safe because I know what I'm doing
unsafe { *ptr }
}
```
## Good Example
```rust
// DO: Explain the safety invariant
fn get_unchecked(slice: &[i32], index: usize) -> i32 {
// SAFETY: Caller guarantees index < slice.len()
unsafe { *slice.get_unchecked(index) }
}
// DO: Be specific about what makes it safe
fn read_header(buffer: &[u8]) -> Header {
assert!(buffer.len() >= std::mem::size_of::<Header>());
// SAFETY:
// - buffer.len() >= size_of::<Header>() (asserted above)
// - buffer is aligned for u8, which is compatible with any alignment
// - Header is #[repr(C)] and has no padding requirements
unsafe {
std::ptr::read_unaligned(buffer.as_ptr() as *const Header)
}
}
// DO: Document unsafe impl
struct MySendType(*mut i32);
// SAFETY: The pointer is to thread-local storage that is only accessed
// from the owning thread. MySendType is only sent when the TLS slot
// is being transferred between threads with proper synchronization.
unsafe impl Send for MySendType {}
// DO: Multi-line for complex invariants
fn complex_operation(data: &mut [u8], ranges: &[(usize, usize)]) {
for &(start, end) in ranges {
// SAFETY:
// 1. All ranges were validated to be within data.len()
// in the calling function `validate_ranges()`
// 2. Ranges are non-overlapping (invariant of RangeSet)
// 3. We have &mut access to data, so no aliasing
unsafe {
let ptr = data.as_mut_ptr().add(start);
std::ptr::write_bytes(ptr, 0, end - start);
}
}
}
```
## SAFETY Comment Format
```rust
// SAFETY: <brief explanation>
// Or for complex cases:
// SAFETY:
// - Invariant 1: explanation
// - Invariant 2: explanation
// - Why this is upheld: explanation
```
## What to Include
1. **What invariants must hold** for this to be safe
2. **Why those invariants hold** at this specific call site
3. **What could go wrong** if the invariants were violated (optional but helpful)
## Clippy Configuration
```toml
# clippy.toml
accept-comment-above-statement = true
accept-comment-above-attributes = true
```
## Checklist
- [ ] Does every unsafe block have a SAFETY comment?
- [ ] Does the comment explain WHY it's safe, not just WHAT it does?
- [ ] Are all relevant invariants mentioned?
- [ ] Would a reviewer understand the safety argument?
## Related Rules
- `safety-02`: Verify safety invariants
- `safety-10`: Add Safety section in docs for public unsafe functions

View File

@@ -1,127 +0,0 @@
---
id: safety-10
original_id: G.UNS.SAS.01
level: G
impact: HIGH
clippy: missing_safety_doc
---
# Add Safety Section in Docs for Public Unsafe Functions
## Summary
Public `unsafe` functions must have a `# Safety` section in their documentation explaining the caller's obligations.
## Rationale
Unlike SAFETY comments (which explain why an unsafe block is sound), `# Safety` docs tell callers what they must guarantee. Without this, users cannot safely call the function.
## Bad Example
```rust
// DON'T: Unsafe function without safety docs
pub unsafe fn process_buffer(ptr: *const u8, len: usize) {
// ...
}
// DON'T: Safety docs that don't explain requirements
/// Processes a buffer.
///
/// This function is unsafe. // Not helpful!
pub unsafe fn process_buffer(ptr: *const u8, len: usize) {
// ...
}
```
## Good Example
```rust
/// Processes a buffer of bytes.
///
/// # Safety
///
/// The caller must ensure that:
///
/// - `ptr` is non-null and properly aligned for `u8`
/// - `ptr` points to at least `len` consecutive, initialized bytes
/// - The memory referenced by `ptr` is not mutated during this call
/// - `len` does not exceed `isize::MAX`
///
/// # Examples
///
/// ```
/// let data = [1u8, 2, 3, 4];
/// // SAFETY: data is a valid slice, we pass its pointer and length
/// unsafe { process_buffer(data.as_ptr(), data.len()) };
/// ```
pub unsafe fn process_buffer(ptr: *const u8, len: usize) {
// ...
}
/// Creates a `Vec<T>` from raw parts.
///
/// # Safety
///
/// This is highly unsafe due to the number of invariants that must
/// be upheld by the caller:
///
/// * `ptr` must have been allocated via the global allocator
/// * `T` must have the same alignment as the original allocation
/// * `capacity` must be the capacity the pointer was allocated with
/// * `length` must be less than or equal to `capacity`
/// * The first `length` values must be properly initialized
/// * The allocated memory must not be used elsewhere
///
/// Violating these may cause undefined behavior including
/// use-after-free, double-free, and memory corruption.
pub unsafe fn from_raw_parts(ptr: *mut T, length: usize, capacity: usize) -> Vec<T> {
// ...
}
```
## Safety Documentation Template
```rust
/// Brief description of what the function does.
///
/// # Safety
///
/// The caller must ensure that:
///
/// - Requirement 1: detailed explanation
/// - Requirement 2: detailed explanation
///
/// # Panics (if applicable)
///
/// Panics if...
///
/// # Examples
///
/// ```
/// // SAFETY: explanation of why this call is safe
/// unsafe { function_name(...) };
/// ```
```
## What to Document
| Category | Example |
|----------|---------|
| Pointer validity | "ptr must be non-null and aligned" |
| Memory state | "must point to initialized memory" |
| Aliasing | "no other references to this memory may exist" |
| Lifetime | "pointer must be valid for the duration of the call" |
| Thread safety | "must not be called concurrently with..." |
| Invariants | "len must not exceed isize::MAX" |
## Checklist
- [ ] Does the function have a `# Safety` section?
- [ ] Are ALL caller obligations listed?
- [ ] Is each requirement specific and verifiable?
- [ ] Does the example show correct usage with SAFETY comment?
## Related Rules
- `safety-09`: SAFETY comments for unsafe blocks
- `safety-02`: Verify safety invariants

View File

@@ -1,105 +0,0 @@
---
id: safety-11
original_id: G.UNS.SAS.02
level: G
impact: MEDIUM
clippy: debug_assert_with_mut_call
---
# Use assert! Instead of debug_assert! in Unsafe Functions
## Summary
In `unsafe` functions or functions containing unsafe blocks, prefer `assert!` over `debug_assert!` for checking safety invariants.
## Rationale
`debug_assert!` is compiled out in release builds. If an invariant is important enough to check for safety, it should be checked in all builds to catch violations.
## Bad Example
```rust
// DON'T: Use debug_assert for safety-critical checks
pub unsafe fn get_unchecked(slice: &[i32], index: usize) -> &i32 {
debug_assert!(index < slice.len()); // Gone in release!
&*slice.as_ptr().add(index)
}
// DON'T: Rely on debug_assert for FFI safety
pub unsafe fn call_c_function(ptr: *const Data) {
debug_assert!(!ptr.is_null()); // Won't catch bugs in release
ffi::process_data(ptr);
}
```
## Good Example
```rust
// DO: Use assert! for safety checks (when performance allows)
pub unsafe fn get_unchecked(slice: &[i32], index: usize) -> &i32 {
assert!(index < slice.len(), "index {} out of bounds for len {}", index, slice.len());
&*slice.as_ptr().add(index)
}
// DO: Use debug_assert when CALLER is responsible
/// # Safety
/// index must be less than slice.len()
pub unsafe fn get_unchecked_fast(slice: &[i32], index: usize) -> &i32 {
// Caller is responsible; debug_assert just helps catch bugs during development
debug_assert!(index < slice.len());
&*slice.as_ptr().add(index)
}
// DO: Use assert for internal safety, debug_assert for caller obligations
pub fn get_checked(slice: &[i32], index: usize) -> Option<&i32> {
if index < slice.len() {
// SAFETY: We just checked index < len
// debug_assert is fine here because the if-check is the real guard
Some(unsafe {
debug_assert!(index < slice.len()); // Redundant, just for documentation
&*slice.as_ptr().add(index)
})
} else {
None
}
}
```
## When to Use Each
| Assertion | Use When |
|-----------|----------|
| `assert!` | Invariant is not already checked; function is called with untrusted input |
| `debug_assert!` | Invariant is the caller's responsibility (documented in `# Safety`); performance-critical |
| No assert | Invariant is enforced by types or prior checks in the same function |
## Hybrid Approach
```rust
// Use cfg to have both safety and performance
pub unsafe fn process(slice: &[u8], index: usize) {
// Always check in tests and debug
#[cfg(any(test, debug_assertions))]
assert!(index < slice.len());
// Optional: paranoid mode for production
#[cfg(feature = "paranoid")]
assert!(index < slice.len());
// SAFETY: Caller guarantees index < len (checked in debug)
let ptr = slice.as_ptr().add(index);
// ...
}
```
## Checklist
- [ ] Is this a safety-critical invariant?
- [ ] Who is responsible for upholding it (caller or this function)?
- [ ] Can the assertion be optimized away when provably true?
- [ ] What's the performance impact of the assertion?
## Related Rules
- `safety-02`: Verify safety invariants
- `safety-09`: SAFETY comments

Some files were not shown because too many files have changed in this diff Show More