Add: windows mvp - transparent bugs not fixed

This commit is contained in:
DaZuo0122
2026-02-12 22:58:33 +08:00
commit 61825f647d
147 changed files with 28498 additions and 0 deletions

View File

@@ -0,0 +1,95 @@
---
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/
user-invocable: false
---
# 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

@@ -0,0 +1,16 @@
# 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

@@ -0,0 +1,6 @@
# 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

@@ -0,0 +1,49 @@
---
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

@@ -0,0 +1,115 @@
---
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

@@ -0,0 +1,216 @@
---
name: core-dynamic-skills
# Command-based tool - no description to prevent auto-triggering
# Triggered by: /sync-crate-skills, /clean-crate-skills, /update-crate-skill
argument-hint: "[--force] | <crate_name>"
context: fork
agent: general-purpose
---
# Dynamic Skills Manager
> **Version:** 2.1.0 | **Last Updated:** 2025-01-27
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
## Execution Mode Detection
**CRITICAL: Check if agent and command infrastructure is available.**
Try to read: `../../agents/` directory
Check if `/create-llms-for-skills` and `/create-skills-via-llms` commands work.
---
## Agent Mode (Plugin Install)
**When full plugin infrastructure is available:**
### 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
```
### 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
### Sync Command
```bash
/sync-crate-skills [--force]
```
1. Parse Cargo.toml for dependencies
2. For each dependency:
- Check if skill exists at `~/.claude/skills/{crate}/`
- If missing (or --force): generate skill
3. Report results
---
## Inline Mode (Skills-only Install)
**When agent/command infrastructure is NOT available, execute manually:**
### Step 1: Parse Cargo.toml
```bash
# Read dependencies
cat Cargo.toml | grep -A 100 '\[dependencies\]' | grep -E '^[a-zA-Z]'
```
Or use Read tool to parse Cargo.toml and extract:
- `[dependencies]` section
- `[dev-dependencies]` section (optional)
- Workspace members (if workspace project)
### Step 2: Check Existing Skills
```bash
# List existing skills
ls ~/.claude/skills/
```
Compare with dependencies to find missing skills.
### Step 3: Generate Missing Skills
For each missing crate:
```bash
# 1. Fetch crate documentation
agent-browser open "https://docs.rs/{crate}/latest/{crate}/"
agent-browser get text ".docblock"
# Save content
# 2. Create skill directory
mkdir -p ~/.claude/skills/{crate}
mkdir -p ~/.claude/skills/{crate}/references
# 3. Create SKILL.md
# Use template from rust-skill-creator inline mode
# 4. Create reference files for key modules
agent-browser open "https://docs.rs/{crate}/latest/{crate}/{module}/"
agent-browser get text ".docblock"
# Save to ~/.claude/skills/{crate}/references/{module}.md
agent-browser close
```
**WebFetch fallback:**
```
WebFetch("https://docs.rs/{crate}/latest/{crate}/", "Extract API documentation overview, key types, and usage examples")
```
### Step 4: Workspace Support
For Cargo workspace projects:
```bash
# 1. Parse root Cargo.toml for workspace members
cat Cargo.toml | grep -A 10 '\[workspace\]'
# 2. For each member, parse their Cargo.toml
for member in members; do
cat ${member}/Cargo.toml | grep -A 100 '\[dependencies\]'
done
# 3. Aggregate and deduplicate dependencies
# 4. Generate skills for missing crates
```
### Clean Command (Inline)
```bash
# Clean specific crate
rm -rf ~/.claude/skills/{crate_name}
# Clean all generated skills
rm -rf ~/.claude/skills/*
```
### Update Command (Inline)
```bash
# Remove old skill
rm -rf ~/.claude/skills/{crate_name}
# Re-generate (same as sync for single crate)
# Follow Step 3 above for the specific crate
```
---
## Local Skills Directory
```
~/.claude/skills/
├── tokio/
│ ├── SKILL.md
│ └── references/
├── serde/
│ ├── SKILL.md
│ └── references/
└── axum/
├── SKILL.md
└── references/
```
---
## 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 (Agent Mode only)
- `/create-skills-via-llms` - Create skills from llms.txt (Agent Mode only)
## Error Handling
| Error | Cause | Solution |
|-------|-------|----------|
| Commands not found | Skills-only install | Use inline mode |
| Cargo.toml not found | Not in Rust project | Navigate to project root |
| docs.rs unavailable | Network issue | Retry or skip crate |
| Permission denied | Directory issue | Check ~/.claude/skills/ permissions |

View File

@@ -0,0 +1,249 @@
---
name: core-fix-skill-docs
# Internal maintenance tool - no description to prevent auto-triggering
# Triggered by: /fix-skill-docs command
argument-hint: "[crate_name] [--check-only]"
context: fork
agent: general-purpose
---
# Fix Skill Documentation
> **Version:** 2.1.0 | **Last Updated:** 2025-01-27
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
## Execution Mode Detection
**CRITICAL: Check if agent infrastructure is available.**
This skill can run in two modes:
- **Agent Mode**: Uses background agents for documentation fetching
- **Inline Mode**: Executes directly using agent-browser CLI or WebFetch
---
## Agent Mode (Plugin Install)
**When agent infrastructure is available, use background agents for fetching:**
### 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: OK
references/:
- sync.md: OK
- runtime.md: MISSING
Action needed: 1 file missing
```
#### 5. Fix Missing Files (Agent Mode)
Launch background agent to fetch documentation:
```
Task(
subagent_type: "general-purpose",
run_in_background: true,
prompt: "Fetch documentation for {crate_name}/{module} from docs.rs.
Use agent-browser CLI to navigate to https://docs.rs/{crate_name}/latest/{crate_name}/{module}/
Extract the main documentation and save to ~/.claude/skills/{crate_name}/references/{module}.md"
)
```
---
## Inline Mode (Skills-only Install)
**When agent infrastructure is NOT available, execute directly:**
### Step 1: Scan Skills Directory
```bash
# List all skills
ls ~/.claude/skills/
# Or check specific skill
ls ~/.claude/skills/{crate_name}/
```
### Step 2: Parse SKILL.md for References
Read SKILL.md and extract all `./references/*.md` patterns:
```bash
# Using Read tool
Read("~/.claude/skills/{crate_name}/SKILL.md")
# Look for lines like:
# - `./references/sync.md` - Sync primitives
# - `./references/runtime.md` - Runtime configuration
```
### Step 3: Check File Existence
```bash
# Check each referenced file
for ref in references; do
if [ ! -f "~/.claude/skills/{crate_name}/references/${ref}.md" ]; then
echo "MISSING: ${ref}.md"
fi
done
```
### Step 4: Report Status
Output format:
```
=== {crate_name} ===
SKILL.md: OK
references/:
- sync.md: OK
- runtime.md: MISSING
Action needed: 1 file missing
```
### Step 5: Fix Missing Files (Inline)
For each missing file:
**Using agent-browser CLI:**
```bash
agent-browser open "https://docs.rs/{crate_name}/latest/{crate_name}/{module}/"
agent-browser get text ".docblock"
# Save output to ~/.claude/skills/{crate_name}/references/{module}.md
agent-browser close
```
**Using WebFetch fallback:**
```
WebFetch("https://docs.rs/{crate_name}/latest/{crate_name}/{module}/",
"Extract the main documentation content for this module")
```
Then write the content:
```bash
Write("~/.claude/skills/{crate_name}/references/{module}.md", <fetched_content>)
```
### Step 6: Update SKILL.md (if --remove-invalid)
If `--remove-invalid` flag is set and file cannot be fetched:
```bash
# Read current SKILL.md
Read("~/.claude/skills/{crate_name}/SKILL.md")
# Remove the invalid reference line
Edit("~/.claude/skills/{crate_name}/SKILL.md",
old_string="- `./references/{invalid_file}.md` - Description",
new_string="")
```
---
## Tool Priority
1. **agent-browser CLI** - Primary tool for fetching documentation
2. **WebFetch** - Fallback if agent-browser unavailable
3. **Edit SKILL.md** - For removing invalid references (--remove-invalid only)
---
## Examples
### Check All Skills (--check-only)
```bash
/fix-skill-docs --check-only
# Output:
=== tokio ===
SKILL.md: OK
references/:
- sync.md: OK
- runtime.md: MISSING
- task.md: OK
=== serde ===
SKILL.md: OK
references/:
- derive.md: OK
Summary: 1 file missing in 1 skill
```
### Fix Specific Crate
```bash
/fix-skill-docs tokio
# Fetches missing runtime.md from docs.rs
# Reports success
```
### Remove Invalid References
```bash
/fix-skill-docs tokio --remove-invalid
# If runtime.md cannot be fetched:
# Removes reference from SKILL.md instead
```
---
## Error Handling
| Error | Cause | Solution |
|-------|-------|----------|
| Agent not available | Skills-only install | Use inline mode |
| Skills directory empty | No skills installed | Run /sync-crate-skills first |
| docs.rs unavailable | Network issue | Retry or use --remove-invalid |
| Permission denied | Directory issue | Check ~/.claude/skills/ permissions |
| Invalid SKILL.md format | Corrupted skill | Re-generate skill |

161
skills/domain-cli/SKILL.md Normal file
View File

@@ -0,0 +1,161 @@
---
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"]
user-invocable: false
---
# 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

@@ -0,0 +1,166 @@
---
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, 云原生, 微服务, 容器"
user-invocable: false
---
# 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

@@ -0,0 +1,178 @@
---
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"]
user-invocable: false
---
## Project Context (Auto-Injected)
**Target configuration:**
!`cat .cargo/config.toml 2>/dev/null || echo "No .cargo/config.toml found"`
---
# 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

@@ -0,0 +1,146 @@
---
name: domain-fintech
description: "Use when building fintech apps. Keywords: fintech, trading, decimal, currency, financial, money, transaction, ledger, payment, exchange rate, precision, rounding, accounting, 金融, 交易系统, 货币, 支付"
user-invocable: false
---
# 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 |

168
skills/domain-iot/SKILL.md Normal file
View File

@@ -0,0 +1,168 @@
---
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, 物联网, 传感器, 边缘计算, 智能家居"
user-invocable: false
---
# 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 |

181
skills/domain-ml/SKILL.md Normal file
View File

@@ -0,0 +1,181 @@
---
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, 机器学习, 人工智能, 模型推理"
user-invocable: false
---
# 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 |

156
skills/domain-web/SKILL.md Normal file
View File

@@ -0,0 +1,156 @@
---
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"]
user-invocable: false
---
# 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

@@ -0,0 +1,134 @@
---
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, 所有权, 借用, 生命周期"
user-invocable: false
---
# 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

@@ -0,0 +1,222 @@
# 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

@@ -0,0 +1,339 @@
# 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

@@ -0,0 +1,265 @@
# 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

@@ -0,0 +1,229 @@
# 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

@@ -0,0 +1,159 @@
---
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, 智能指针, 引用计数, 堆分配"
user-invocable: false
---
# 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

@@ -0,0 +1,153 @@
---
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, 可变性, 内部可变性, 借用冲突"
user-invocable: false
---
# 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

@@ -0,0 +1,165 @@
---
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, 泛型, 特征, 零成本抽象, 单态化"
user-invocable: false
---
# 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

@@ -0,0 +1,175 @@
---
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, 类型状态, 新类型模式, 类型驱动设计"
user-invocable: false
---
# 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

@@ -0,0 +1,166 @@
---
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"
user-invocable: false
---
# 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

@@ -0,0 +1,332 @@
# 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

@@ -0,0 +1,404 @@
# 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

@@ -0,0 +1,222 @@
---
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, 并发, 线程, 异步, 死锁"
user-invocable: false
---
# 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

@@ -0,0 +1,312 @@
# 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

@@ -0,0 +1,396 @@
# 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

@@ -0,0 +1,409 @@
# 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

@@ -0,0 +1,331 @@
# 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"),
}
}
```

174
skills/m09-domain/SKILL.md Normal file
View File

@@ -0,0 +1,174 @@
---
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, 领域模型, 领域驱动设计, 业务规则"
user-invocable: false
---
# 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

@@ -0,0 +1,157 @@
---
name: m10-performance
description: "CRITICAL: Use for performance optimization. Triggers: performance, optimization, benchmark, profiling, flamegraph, criterion, slow, fast, allocation, cache, SIMD, make it faster, 性能优化, 基准测试"
user-invocable: false
---
# 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

@@ -0,0 +1,365 @@
# 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

@@ -0,0 +1,162 @@
---
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 绑定"
user-invocable: false
---
## Current Dependencies (Auto-Injected)
!`grep -A 100 '^\[dependencies\]' Cargo.toml 2>/dev/null | head -30 || echo "No Cargo.toml found"`
---
# 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

@@ -0,0 +1,177 @@
---
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 模式"
user-invocable: false
---
# 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

@@ -0,0 +1,180 @@
---
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, 领域错误, 错误分类, 恢复策略, 重试, 熔断器, 优雅降级"
user-invocable: false
---
# 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

@@ -0,0 +1,177 @@
---
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"
user-invocable: false
---
# 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

@@ -0,0 +1,286 @@
# 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

@@ -0,0 +1,160 @@
---
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, 反模式, 常见错误, 代码异味, 最佳实践, 地道写法"
user-invocable: false
---
# 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

@@ -0,0 +1,421 @@
# 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

@@ -0,0 +1,352 @@
---
name: meta-cognition-parallel
description: "EXPERIMENTAL: Three-layer parallel meta-cognition analysis. Triggers on: /meta-parallel, 三层分析, parallel analysis, 并行元认知"
argument-hint: "<rust_question>"
---
# Meta-Cognition Parallel Analysis (Experimental)
> **Status:** Experimental | **Version:** 0.2.0 | **Last Updated:** 2025-01-27
>
> This skill tests parallel three-layer cognitive analysis.
## Concept
Instead of sequential analysis, this skill launches three parallel analyzers - one for each cognitive layer - then synthesizes their results.
```
User Question
┌─────────────────────────────────────────────────────┐
│ meta-cognition-parallel │
│ (Coordinator) │
└─────────────────────────────────────────────────────┘
├─── Layer 1 ──► Language Mechanics ──► L1 Result
├─── Layer 2 ──► Design Choices ──► L2 Result
│ ├── Parallel (Agent Mode)
│ │ or Sequential (Inline)
└─── Layer 3 ──► Domain Constraints ──► L3 Result
┌─────────────────────────────────────────────────────┐
│ Cross-Layer Synthesis │
│ (In main context with all results) │
└─────────────────────────────────────────────────────┘
Domain-Correct Architectural Solution
```
## Usage
```
/meta-parallel <your Rust question>
```
**Example:**
```
/meta-parallel 我的交易系统报 E0382 错误,应该用 clone 吗?
```
## Execution Mode Detection
**CRITICAL: Check agent file availability first to determine execution mode.**
Try to read layer analyzer files:
- `../../agents/layer1-analyzer.md`
- `../../agents/layer2-analyzer.md`
- `../../agents/layer3-analyzer.md`
---
## Agent Mode (Plugin Install) - Parallel Execution
**When all layer analyzer files exist at `../../agents/`:**
### Step 1: Parse User Query
Extract from `$ARGUMENTS`:
- The original question
- Any code snippets
- Domain hints (trading, web, embedded, etc.)
### Step 2: Launch Three Parallel Agents
**CRITICAL: Launch all three Tasks in a SINGLE message to enable parallel execution.**
```
Read agent files, then launch in parallel:
Task(
subagent_type: "general-purpose",
run_in_background: true,
prompt: <content of ../../agents/layer1-analyzer.md>
+ "\n\n## User Query\n" + $ARGUMENTS
)
Task(
subagent_type: "general-purpose",
run_in_background: true,
prompt: <content of ../../agents/layer2-analyzer.md>
+ "\n\n## User Query\n" + $ARGUMENTS
)
Task(
subagent_type: "general-purpose",
run_in_background: true,
prompt: <content of ../../agents/layer3-analyzer.md>
+ "\n\n## User Query\n" + $ARGUMENTS
)
```
### Step 3: Collect Results
Wait for all three agents to complete. Each returns structured analysis.
### Step 4: Cross-Layer Synthesis
With all three results, perform synthesis per template below.
---
## Inline Mode (Skills-only Install) - Sequential Execution
**When layer analyzer files are NOT available, execute analysis directly:**
### Step 1: Parse User Query
Same as Agent Mode - extract question, code, and domain hints from `$ARGUMENTS`.
### Step 2: Execute Layer 1 - Language Mechanics
Analyze the Rust language mechanics involved:
```markdown
## Layer 1: Language Mechanics
**Error/Pattern Identified:**
- Error code: E0XXX (if applicable)
- Pattern: ownership/borrowing/lifetime/etc.
**Root Cause:**
[Explain why this error occurs in terms of Rust's ownership model]
**Language-Level Solutions:**
1. [Solution 1]: description
2. [Solution 2]: description
**Confidence:** HIGH | MEDIUM | LOW
**Reasoning:** [Why this confidence level]
```
**Focus areas:**
- Ownership rules (move, copy, borrow)
- Lifetime annotations
- Borrowing rules (shared vs mutable)
- Error codes and their meanings
### Step 3: Execute Layer 2 - Design Choices
Analyze the design patterns and trade-offs:
```markdown
## Layer 2: Design Choices
**Design Pattern Context:**
- Current approach: [What pattern is being used]
- Problem: [Why it conflicts with Rust's rules]
**Design Alternatives:**
| Pattern | Pros | Cons | When to Use |
|---------|------|------|-------------|
| Pattern A | ... | ... | ... |
| Pattern B | ... | ... | ... |
**Recommended Pattern:**
[Which pattern fits best and why]
**Confidence:** HIGH | MEDIUM | LOW
**Reasoning:** [Why this confidence level]
```
**Focus areas:**
- Smart pointer choices (Box, Rc, Arc)
- Interior mutability patterns (Cell, RefCell, Mutex)
- Ownership transfer vs sharing
- Cloning vs references
### Step 4: Execute Layer 3 - Domain Constraints
Analyze domain-specific requirements:
```markdown
## Layer 3: Domain Constraints
**Domain Identified:** [trading/fintech | web | CLI | embedded | etc.]
**Domain-Specific Requirements:**
- [ ] Performance: [requirements]
- [ ] Safety: [requirements]
- [ ] Concurrency: [requirements]
- [ ] Auditability: [requirements]
**Domain Best Practices:**
1. [Best practice 1]
2. [Best practice 2]
**Constraints on Solution:**
- MUST: [hard requirements]
- SHOULD: [soft requirements]
- AVOID: [anti-patterns for this domain]
**Confidence:** HIGH | MEDIUM | LOW
**Reasoning:** [Why this confidence level]
```
**Focus areas:**
- Industry requirements (FinTech regulations, web scalability, etc.)
- Performance constraints
- Safety and correctness requirements
- Common patterns in the domain
### Step 5: Cross-Layer Synthesis
Combine all three layers:
```markdown
## Cross-Layer Synthesis
### Layer Results Summary
| Layer | Key Finding | Confidence |
|-------|-------------|------------|
| L1 (Mechanics) | [Summary] | [Level] |
| L2 (Design) | [Summary] | [Level] |
| L3 (Domain) | [Summary] | [Level] |
### Cross-Layer Reasoning
1. **L3 → L2:** [How domain constraints affect design choice]
2. **L2 → L1:** [How design choice determines mechanism]
3. **L1 ← L3:** [Direct domain impact on language features]
### Synthesized Recommendation
**Problem:** [Restated with full context]
**Solution:** [Domain-correct architectural solution]
**Rationale:**
- Domain requires: [L3 constraint]
- Design pattern: [L2 pattern]
- Mechanism: [L1 implementation]
### Confidence Assessment
- **Overall:** HIGH | MEDIUM | LOW
- **Limiting Factor:** [Which layer had lowest confidence]
```
---
## Output Template
Both modes produce the same output format:
```markdown
# Three-Layer Meta-Cognition Analysis
> Query: [User's question]
---
## Layer 1: Language Mechanics
[L1 analysis result]
---
## Layer 2: Design Choices
[L2 analysis result]
---
## Layer 3: Domain Constraints
[L3 analysis result]
---
## Cross-Layer Synthesis
### Reasoning Chain
```
L3 Domain: [Constraint]
↓ implies
L2 Design: [Pattern]
↓ implemented via
L1 Mechanism: [Feature]
```
### Final Recommendation
**Do:** [Recommended approach]
**Don't:** [What to avoid]
**Code Pattern:**
```rust
// Recommended implementation
```
---
*Analysis performed by meta-cognition-parallel v0.2.0 (experimental)*
```
---
## Test Scenarios
### Test 1: Trading System E0382
```
/meta-parallel 交易系统报 E0382trade record 被 move 了
```
Expected: L3 identifies FinTech constraints → L2 suggests shared immutable → L1 recommends Arc<T>
### Test 2: Web API Concurrency
```
/meta-parallel Web API 中多个 handler 需要共享数据库连接池
```
Expected: L3 identifies Web constraints → L2 suggests connection pooling → L1 recommends Arc<Pool>
### Test 3: CLI Tool Config
```
/meta-parallel CLI 工具如何处理配置文件和命令行参数的优先级
```
Expected: L3 identifies CLI constraints → L2 suggests config precedence pattern → L1 recommends builder pattern
---
## Error Handling
| Error | Cause | Solution |
|-------|-------|----------|
| Agent files not found | Skills-only install | Use inline mode (sequential) |
| Agent timeout | Complex analysis | Wait longer or use inline mode |
| Incomplete layer result | Agent issue | Fill in with inline analysis |
## Limitations
- **Agent Mode:** Parallel execution, faster but requires plugin install
- **Inline Mode:** Sequential execution, slower but works everywhere
- Cross-layer synthesis quality depends on result structure
- May have higher latency than simple single-layer analysis
## Feedback
This is experimental. Please report issues and suggestions to improve the three-layer analysis approach.

View File

@@ -0,0 +1,206 @@
---
name: rust-call-graph
description: "Visualize Rust function call graphs using LSP. Triggers on: /call-graph, call hierarchy, who calls, what calls, 调用图, 调用关系, 谁调用了, 调用了谁"
argument-hint: "<function_name> [--depth N] [--direction in|out|both]"
allowed-tools: ["LSP", "Read", "Glob"]
---
# Rust Call Graph
Visualize function call relationships using LSP call hierarchy.
## Usage
```
/rust-call-graph <function_name> [--depth N] [--direction in|out|both]
```
**Options:**
- `--depth N`: How many levels to traverse (default: 3)
- `--direction`: `in` (callers), `out` (callees), `both`
**Examples:**
- `/rust-call-graph process_request` - Show both callers and callees
- `/rust-call-graph handle_error --direction in` - Show only callers
- `/rust-call-graph main --direction out --depth 5` - Deep callee analysis
## LSP Operations
### 1. Prepare Call Hierarchy
Get the call hierarchy item for a function.
```
LSP(
operation: "prepareCallHierarchy",
filePath: "src/handler.rs",
line: 45,
character: 8
)
```
### 2. Incoming Calls (Who calls this?)
```
LSP(
operation: "incomingCalls",
filePath: "src/handler.rs",
line: 45,
character: 8
)
```
### 3. Outgoing Calls (What does this call?)
```
LSP(
operation: "outgoingCalls",
filePath: "src/handler.rs",
line: 45,
character: 8
)
```
## Workflow
```
User: "Show call graph for process_request"
[1] Find function location
LSP(workspaceSymbol) or Grep
[2] Prepare call hierarchy
LSP(prepareCallHierarchy)
[3] Get incoming calls (callers)
LSP(incomingCalls)
[4] Get outgoing calls (callees)
LSP(outgoingCalls)
[5] Recursively expand to depth N
[6] Generate ASCII visualization
```
## Output Format
### Incoming Calls (Who calls this?)
```
## Callers of `process_request`
main
└── run_server
└── handle_connection
└── process_request ◄── YOU ARE HERE
```
### Outgoing Calls (What does this call?)
```
## Callees of `process_request`
process_request ◄── YOU ARE HERE
├── parse_headers
│ └── validate_header
├── authenticate
│ ├── check_token
│ └── load_user
├── execute_handler
│ └── [dynamic dispatch]
└── send_response
└── serialize_body
```
### Bidirectional (Both)
```
## Call Graph for `process_request`
┌─────────────────┐
│ main │
└────────┬────────┘
┌────────▼────────┐
│ run_server │
└────────┬────────┘
┌────────▼────────┐
│handle_connection│
└────────┬────────┘
┌────────────────────┼────────────────────┐
│ │ │
┌───────▼───────┐ ┌───────▼───────┐ ┌───────▼───────┐
│ parse_headers │ │ authenticate │ │send_response │
└───────────────┘ └───────┬───────┘ └───────────────┘
┌───────┴───────┐
│ │
┌──────▼──────┐ ┌──────▼──────┐
│ check_token │ │ load_user │
└─────────────┘ └─────────────┘
```
## Analysis Insights
After generating the call graph, provide insights:
```
## Analysis
**Entry Points:** main, test_process_request
**Leaf Functions:** validate_header, serialize_body
**Hot Path:** main → run_server → handle_connection → process_request
**Complexity:** 12 functions, 3 levels deep
**Potential Issues:**
- `authenticate` has high fan-out (4 callees)
- `process_request` is called from 3 places (consider if this is intentional)
```
## Common Patterns
| User Says | Direction | Use Case |
|-----------|-----------|----------|
| "Who calls X?" | incoming | Impact analysis |
| "What does X call?" | outgoing | Understanding implementation |
| "Show call graph" | both | Full picture |
| "Trace from main to X" | outgoing | Execution path |
## Visualization Options
| Style | Best For |
|-------|----------|
| Tree (default) | Simple hierarchies |
| Box diagram | Complex relationships |
| Flat list | Many connections |
| Mermaid | Export to docs |
### Mermaid Export
```mermaid
graph TD
main --> run_server
run_server --> handle_connection
handle_connection --> process_request
process_request --> parse_headers
process_request --> authenticate
process_request --> send_response
```
## Related Skills
| When | See |
|------|-----|
| Find definition | rust-code-navigator |
| Project structure | rust-symbol-analyzer |
| Trait implementations | rust-trait-explorer |
| Safe refactoring | rust-refactor-helper |

View File

@@ -0,0 +1,159 @@
---
name: rust-code-navigator
description: "Navigate Rust code using LSP. Triggers on: /navigate, go to definition, find references, where is defined, 跳转定义, 查找引用, 定义在哪, 谁用了这个"
argument-hint: "<symbol> [in file.rs:line]"
allowed-tools: ["LSP", "Read", "Glob"]
---
# Rust Code Navigator
Navigate large Rust codebases efficiently using Language Server Protocol.
## Usage
```
/rust-code-navigator <symbol> [in file.rs:line]
```
**Examples:**
- `/rust-code-navigator parse_config` - Find definition of parse_config
- `/rust-code-navigator MyStruct in src/lib.rs:42` - Navigate from specific location
## LSP Operations
### 1. Go to Definition
Find where a symbol is defined.
```
LSP(
operation: "goToDefinition",
filePath: "src/main.rs",
line: 25,
character: 10
)
```
**Use when:**
- User asks "where is X defined?"
- User wants to understand a type/function
- Ctrl+click equivalent
### 2. Find References
Find all usages of a symbol.
```
LSP(
operation: "findReferences",
filePath: "src/lib.rs",
line: 15,
character: 8
)
```
**Use when:**
- User asks "who uses X?"
- Before refactoring/renaming
- Understanding impact of changes
### 3. Hover Information
Get type and documentation for a symbol.
```
LSP(
operation: "hover",
filePath: "src/main.rs",
line: 30,
character: 15
)
```
**Use when:**
- User asks "what type is X?"
- User wants documentation
- Quick type checking
## Workflow
```
User: "Where is the Config struct defined?"
[1] Search for "Config" in workspace
LSP(operation: "workspaceSymbol", ...)
[2] If multiple results, ask user to clarify
[3] Go to definition
LSP(operation: "goToDefinition", ...)
[4] Show file path and context
Read surrounding code for context
```
## Output Format
### Definition Found
```
## Config (struct)
**Defined in:** `src/config.rs:15`
```rust
#[derive(Debug, Clone)]
pub struct Config {
pub name: String,
pub port: u16,
pub debug: bool,
}
```
**Documentation:** Configuration for the application server.
```
### References Found
```
## References to `Config` (5 found)
| Location | Context |
|----------|---------|
| src/main.rs:10 | `let config = Config::load()?;` |
| src/server.rs:25 | `fn new(config: Config) -> Self` |
| src/server.rs:42 | `self.config.port` |
| src/tests.rs:15 | `Config::default()` |
| src/cli.rs:8 | `config: Option<Config>` |
```
## Common Patterns
| User Says | LSP Operation |
|-----------|---------------|
| "Where is X defined?" | goToDefinition |
| "Who uses X?" | findReferences |
| "What type is X?" | hover |
| "Find all structs" | workspaceSymbol |
| "What's in this file?" | documentSymbol |
## Error Handling
| Error | Cause | Solution |
|-------|-------|----------|
| "No LSP server" | rust-analyzer not running | Suggest: `rustup component add rust-analyzer` |
| "Symbol not found" | Typo or not in scope | Search with workspaceSymbol first |
| "Multiple definitions" | Generics or macros | Show all and let user choose |
## Related Skills
| When | See |
|------|-----|
| Call relationships | rust-call-graph |
| Project structure | rust-symbol-analyzer |
| Trait implementations | rust-trait-explorer |
| Safe refactoring | rust-refactor-helper |

233
skills/rust-daily/SKILL.md Normal file
View File

@@ -0,0 +1,233 @@
---
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 动态
argument-hint: "[today|week|month]"
context: fork
agent: Explore
---
# Rust Daily Report
> **Version:** 2.1.0 | **Last Updated:** 2025-01-27
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 Mode Detection
**CRITICAL: Check agent file availability first to determine execution mode.**
Try to read: `../../agents/rust-daily-reporter.md`
---
## Agent Mode (Plugin Install)
**When `../../agents/rust-daily-reporter.md` exists:**
### Workflow
```
1. Read: ../../agents/rust-daily-reporter.md
2. Task(subagent_type: "general-purpose", run_in_background: false, prompt: <agent content>)
3. Wait for result
4. Format and present to user
```
---
## Inline Mode (Skills-only Install)
**When agent file is NOT available, execute each source directly:**
### 1. Reddit r/rust
```bash
# Using agent-browser CLI
agent-browser open "https://www.reddit.com/r/rust/hot/"
agent-browser get text ".Post" --limit 10
agent-browser close
```
**Or with WebFetch fallback:**
```
WebFetch("https://www.reddit.com/r/rust/hot/", "Extract top 10 posts with scores and titles")
```
**Parse output into:**
| Score | Title | Link |
|-------|-------|------|
### 2. This Week in Rust
```bash
# Check actionbook first
mcp__actionbook__search_actions("this week in rust")
mcp__actionbook__get_action_by_id(<action_id>)
# Then fetch
agent-browser open "https://this-week-in-rust.org/"
agent-browser get text "<selector_from_actionbook>"
agent-browser close
```
**Parse output into:**
- Issue #{number} ({date}): highlights
### 3. Rust Blog (Official)
```bash
agent-browser open "https://blog.rust-lang.org/"
agent-browser get text "article" --limit 5
agent-browser close
```
**Or with WebFetch fallback:**
```
WebFetch("https://blog.rust-lang.org/", "Extract latest 5 blog posts with dates and titles")
```
**Parse output into:**
| Date | Title | Summary |
|------|-------|---------|
### 4. Inside Rust
```bash
agent-browser open "https://blog.rust-lang.org/inside-rust/"
agent-browser get text "article" --limit 3
agent-browser close
```
**Or with WebFetch fallback:**
```
WebFetch("https://blog.rust-lang.org/inside-rust/", "Extract latest 3 posts with dates and titles")
```
### 5. Rust Foundation
```bash
# News
agent-browser open "https://rustfoundation.org/media/category/news/"
agent-browser get text "article" --limit 3
agent-browser close
# Blog
agent-browser open "https://rustfoundation.org/media/category/blog/"
agent-browser get text "article" --limit 3
agent-browser close
# Events
agent-browser open "https://rustfoundation.org/events/"
agent-browser get text "article" --limit 3
agent-browser close
```
### Time Filtering
After fetching all sources, filter by time range:
| Range | Filter |
|-------|--------|
| day | Last 24 hours |
| week | Last 7 days |
| month | Last 30 days |
### Combining Results
After fetching all sources, combine into the output format below.
---
## Tool Chain Priority
Both modes use the same tool chain order:
1. **actionbook MCP** - Check for cached/pre-fetched content first
```
mcp__actionbook__search_actions("rust news {date}")
mcp__actionbook__search_actions("this week in rust")
mcp__actionbook__search_actions("rust blog")
```
2. **agent-browser CLI** - For dynamic web content
```bash
agent-browser open "<url>"
agent-browser get text "<selector>"
agent-browser close
```
3. **WebFetch** - Fallback if agent-browser unavailable
| Source | Primary Tool | Fallback |
|--------|--------------|----------|
| Reddit | agent-browser | WebFetch |
| TWIR | actionbook → agent-browser | WebFetch |
| Rust Blog | actionbook → WebFetch | - |
| Foundation | actionbook → WebFetch | - |
**DO NOT use:**
- Chrome MCP directly
- WebSearch for fetching news pages
---
## Output Format
```markdown
# Rust {Weekly|Daily|Monthly} Report
**Time Range:** {start} - {end}
## Ecosystem
### Reddit r/rust
| Score | Title | Link |
|-------|-------|------|
| {score} | {title} | [link]({url}) |
### This Week in Rust
- Issue #{number} ({date}): highlights
## Official
| Date | Title | Summary |
|------|-------|---------|
| {date} | {title} | {summary} |
## Foundation
| Date | Title | Summary |
|------|-------|---------|
| {date} | {title} | {summary} |
```
---
## Validation
- Each source should have at least 1 result, otherwise mark "No updates"
- On fetch failure, retry with alternative tool
- Report reason if all tools fail for a source
## Error Handling
| Error | Cause | Solution |
|-------|-------|----------|
| Agent file not found | Skills-only install | Use inline mode |
| agent-browser unavailable | CLI not installed | Use WebFetch |
| Site timeout | Network issues | Retry once, then skip source |
| Empty results | Selector mismatch | Report and use fallback |

View File

@@ -0,0 +1,114 @@
---
name: rust-deps-visualizer
description: "Visualize Rust project dependencies as ASCII art. Triggers on: /deps-viz, dependency graph, show dependencies, visualize deps, 依赖图, 依赖可视化, 显示依赖"
argument-hint: "[--depth N] [--features]"
allowed-tools: ["Bash", "Read", "Glob"]
---
# Rust Dependencies Visualizer
Generate ASCII art visualizations of your Rust project's dependency tree.
## Usage
```
/rust-deps-visualizer [--depth N] [--features]
```
**Options:**
- `--depth N`: Limit tree depth (default: 3)
- `--features`: Show feature flags
## Output Format
### Simple Tree (Default)
```
my-project v0.1.0
├── tokio v1.49.0
│ ├── pin-project-lite v0.2.x
│ └── bytes v1.x
├── serde v1.0.x
│ └── serde_derive v1.0.x
└── anyhow v1.x
```
### Feature-Aware Tree
```
my-project v0.1.0
├── tokio v1.49.0 [rt, rt-multi-thread, macros, fs, io-util]
│ ├── pin-project-lite v0.2.x
│ └── bytes v1.x
├── serde v1.0.x [derive]
│ └── serde_derive v1.0.x (proc-macro)
└── anyhow v1.x [std]
```
## Implementation
**Step 1:** Parse Cargo.toml for direct dependencies
```bash
cargo metadata --format-version=1 --no-deps 2>/dev/null
```
**Step 2:** Get full dependency tree
```bash
cargo tree --depth=${DEPTH:-3} ${FEATURES:+--features} 2>/dev/null
```
**Step 3:** Format as ASCII art tree
Use these box-drawing characters:
- `├──` for middle items
- `└──` for last items
- `│ ` for continuation lines
## Visual Enhancements
### Dependency Categories
```
my-project v0.1.0
├─[Runtime]─────────────────────
│ ├── tokio v1.49.0
│ └── async-trait v0.1.x
├─[Serialization]───────────────
│ ├── serde v1.0.x
│ └── serde_json v1.x
└─[Development]─────────────────
├── criterion v0.5.x
└── proptest v1.x
```
### Size Visualization (Optional)
```
my-project v0.1.0
├── tokio v1.49.0 ████████████ 2.1 MB
├── serde v1.0.x ███████ 1.2 MB
├── regex v1.x █████ 890 KB
└── anyhow v1.x ██ 120 KB
─────────────────
Total: 4.3 MB
```
## Workflow
1. Check for Cargo.toml in current directory
2. Run `cargo tree` with specified options
3. Parse output and generate ASCII visualization
4. Optionally categorize by purpose (runtime, dev, build)
## Related Skills
| When | See |
|------|-----|
| Crate selection advice | m11-ecosystem |
| Workspace management | m11-ecosystem |
| Feature flag decisions | m11-ecosystem |

View File

@@ -0,0 +1,310 @@
---
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 版本, 新特性, 有什么特性"
allowed-tools: ["Task", "Read", "Glob", "mcp__actionbook__*", "Bash"]
---
# Rust Learner
> **Version:** 2.1.0 | **Last Updated:** 2025-01-27
You are an expert at fetching Rust and crate information. Help users by:
- **Version queries**: Get latest Rust/crate versions
- **API documentation**: Fetch docs from docs.rs
- **Changelog**: Get Rust version features from releases.rs
**Primary skill for fetching Rust/crate information.**
## Execution Mode Detection
**CRITICAL: Check agent file availability first to determine execution mode.**
Try to read the agent file for your query type. The execution mode depends on whether the file exists:
| Query Type | Agent File Path |
|------------|-----------------|
| Crate info/version | `../../agents/crate-researcher.md` |
| Rust version features | `../../agents/rust-changelog.md` |
| Std library docs | `../../agents/std-docs-researcher.md` |
| Third-party crate docs | `../../agents/docs-researcher.md` |
| Clippy lints | `../../agents/clippy-researcher.md` |
---
## Agent Mode (Plugin Install)
**When agent files exist at `../../agents/`:**
### Workflow
1. Read the appropriate agent file (relative to this skill)
2. Launch Task with `run_in_background: true`
3. Continue with other work or wait for completion
4. Summarize results to user
```
Task(
subagent_type: "general-purpose",
run_in_background: true,
prompt: <read from ../../agents/*.md file>
)
```
### 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 |
### Agent Mode Examples
**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: <agent content>)
3. Wait for agent
4. Summarize results
```
**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: <agent content>)
3. Wait for agent
4. Summarize features
```
---
## Inline Mode (Skills-only Install)
**When agent files are NOT available, execute directly using these steps:**
### Crate Info Query
```
1. actionbook: mcp__actionbook__search_actions("lib.rs crate info")
2. Get action details: mcp__actionbook__get_action_by_id(<action_id>)
3. agent-browser CLI (or WebFetch fallback):
- open "https://lib.rs/crates/{crate_name}"
- get text using selector from actionbook
- close
4. Parse and format output
```
**Output Format:**
```markdown
## {Crate Name}
**Version:** {latest}
**Description:** {description}
**Features:**
- `feature1`: description
**Links:**
- [docs.rs](https://docs.rs/{crate}) | [crates.io](https://crates.io/crates/{crate}) | [repo]({repo_url})
```
### Rust Version Query
```
1. actionbook: mcp__actionbook__search_actions("releases.rs rust changelog")
2. Get action details for selectors
3. agent-browser CLI (or WebFetch fallback):
- open "https://releases.rs/docs/1.{version}.0/"
- get text using selector from actionbook
- close
4. Parse and format output
```
**Output Format:**
```markdown
## Rust 1.{version}
**Release Date:** {date}
### Language Features
- Feature 1: description
- Feature 2: description
### Library Changes
- std::module: new API
### Stabilized APIs
- `api_name`: description
```
### Std Library Docs (std::*, Send, Sync, Arc, etc.)
```
1. Construct URL: "https://doc.rust-lang.org/std/{path}/"
- Traits: std/{module}/trait.{Name}.html
- Structs: std/{module}/struct.{Name}.html
- Modules: std/{module}/index.html
2. agent-browser CLI (or WebFetch fallback):
- open <url>
- get text "main .docblock"
- close
3. Parse and format output
```
**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` |
**Output Format:**
```markdown
## std::{path}::{Name}
**Signature:**
```rust
{signature}
```
**Description:**
{description}
**Examples:**
```rust
{example_code}
```
```
### Third-Party Crate Docs (tokio, serde, etc.)
```
1. Construct URL: "https://docs.rs/{crate}/latest/{crate}/{path}"
2. agent-browser CLI (or WebFetch fallback):
- open <url>
- get text ".docblock"
- close
3. Parse and format output
```
**Output Format:**
```markdown
## {crate}::{path}
**Signature:**
```rust
{signature}
```
**Description:**
{description}
**Examples:**
```rust
{example_code}
```
```
### Clippy Lints
```
1. agent-browser CLI (or WebFetch fallback):
- open "https://rust-lang.github.io/rust-clippy/stable/"
- search for lint name in page
- get text ".lint-doc" for matching lint
- close
2. Parse and format output
```
**Output Format:**
```markdown
## Clippy Lint: {lint_name}
**Level:** {warn|deny|allow}
**Category:** {category}
**Description:**
{what_it_checks}
**Example (Bad):**
```rust
{bad_code}
```
**Example (Good):**
```rust
{good_code}
```
```
---
## Tool Chain Priority
Both modes use the same tool chain order:
1. **actionbook MCP** - Get pre-computed selectors first
- `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
---
## Deprecated Patterns
| Deprecated | Use Instead | Reason |
|------------|-------------|--------|
| WebSearch for crate info | Task + agent or inline mode | Structured data |
| Direct WebFetch | actionbook + agent-browser | Pre-computed selectors |
| Guessing version numbers | Always fetch from source | Prevents misinformation |
## Error Handling
| Error | Cause | Solution |
|-------|-------|----------|
| Agent file not found | Skills-only install | Use inline mode |
| 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. Use agents or inline mode instead.**

View File

@@ -0,0 +1,273 @@
---
name: rust-refactor-helper
description: "Safe Rust refactoring with LSP analysis. Triggers on: /refactor, rename symbol, move function, extract, 重构, 重命名, 提取函数, 安全重构"
argument-hint: "<action> <target> [--dry-run]"
allowed-tools: ["LSP", "Read", "Glob", "Grep", "Edit"]
---
# Rust Refactor Helper
Perform safe refactoring with comprehensive impact analysis.
## Usage
```
/rust-refactor-helper <action> <target> [--dry-run]
```
**Actions:**
- `rename <old> <new>` - Rename symbol
- `extract-fn <selection>` - Extract to function
- `inline <fn>` - Inline function
- `move <symbol> <dest>` - Move to module
**Examples:**
- `/rust-refactor-helper rename parse_config load_config`
- `/rust-refactor-helper extract-fn src/main.rs:20-35`
- `/rust-refactor-helper move UserService src/services/`
## LSP Operations Used
### Pre-Refactor Analysis
```
# Find all references before renaming
LSP(
operation: "findReferences",
filePath: "src/lib.rs",
line: 25,
character: 8
)
# Get symbol info
LSP(
operation: "hover",
filePath: "src/lib.rs",
line: 25,
character: 8
)
# Check call hierarchy for move operations
LSP(
operation: "incomingCalls",
filePath: "src/lib.rs",
line: 25,
character: 8
)
```
## Refactoring Workflows
### 1. Rename Symbol
```
User: "Rename parse_config to load_config"
[1] Find symbol definition
LSP(goToDefinition)
[2] Find ALL references
LSP(findReferences)
[3] Categorize by file
[4] Check for conflicts
- Is 'load_config' already used?
- Are there macro-generated uses?
[5] Show impact analysis (--dry-run)
[6] Apply changes with Edit tool
```
**Output:**
```
## Rename: parse_config → load_config
### Impact Analysis
**Definition:** src/config.rs:25
**References found:** 8
| File | Line | Context | Change |
|------|------|---------|--------|
| src/config.rs | 25 | `pub fn parse_config(` | Definition |
| src/config.rs | 45 | `parse_config(path)?` | Call |
| src/main.rs | 12 | `config::parse_config` | Import |
| src/main.rs | 30 | `let cfg = parse_config(` | Call |
| src/lib.rs | 8 | `pub use config::parse_config` | Re-export |
| tests/config_test.rs | 15 | `parse_config("test.toml")` | Test |
| tests/config_test.rs | 25 | `parse_config("")` | Test |
| docs/api.md | 42 | `parse_config` | Documentation |
### Potential Issues
⚠️ **Documentation reference:** docs/api.md:42 may need manual update
⚠️ **Re-export:** src/lib.rs:8 - public API change
### Proceed?
- [x] --dry-run (preview only)
- [ ] Apply changes
```
### 2. Extract Function
```
User: "Extract lines 20-35 in main.rs to a function"
[1] Read the selected code block
[2] Analyze variables
- Which are inputs? (used but not defined in block)
- Which are outputs? (defined and used after block)
- Which are local? (defined and used only in block)
[3] Determine function signature
[4] Check for early returns, loops, etc.
[5] Generate extracted function
[6] Replace original code with call
```
**Output:**
```
## Extract Function: src/main.rs:20-35
### Selected Code
```rust
let file = File::open(&path)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
let config: Config = toml::from_str(&contents)?;
validate_config(&config)?;
```
### Analysis
**Inputs:** path: &Path
**Outputs:** config: Config
**Side Effects:** File I/O, may return error
### Extracted Function
```rust
fn load_and_validate_config(path: &Path) -> Result<Config> {
let file = File::open(path)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
let config: Config = toml::from_str(&contents)?;
validate_config(&config)?;
Ok(config)
}
```
### Replacement
```rust
let config = load_and_validate_config(&path)?;
```
```
### 3. Move Symbol
```
User: "Move UserService to src/services/"
[1] Find symbol and all its dependencies
[2] Find all references (callers)
LSP(findReferences)
[3] Analyze import changes needed
[4] Check for circular dependencies
[5] Generate move plan
```
**Output:**
```
## Move: UserService → src/services/user.rs
### Current Location
src/handlers/auth.rs:50-120
### Dependencies (will be moved together)
- struct UserService (50-80)
- impl UserService (82-120)
- const DEFAULT_TIMEOUT (48)
### Import Changes Required
| File | Current | New |
|------|---------|-----|
| src/main.rs | `use handlers::auth::UserService` | `use services::user::UserService` |
| src/handlers/api.rs | `use super::auth::UserService` | `use crate::services::user::UserService` |
| tests/auth_test.rs | `use crate::handlers::auth::UserService` | `use crate::services::user::UserService` |
### New File Structure
```
src/
├── services/
│ ├── mod.rs (NEW - add `pub mod user;`)
│ └── user.rs (NEW - UserService moved here)
├── handlers/
│ └── auth.rs (UserService removed)
```
### Circular Dependency Check
✅ No circular dependencies detected
```
## Safety Checks
| Check | Purpose |
|-------|---------|
| Reference completeness | Ensure all uses are found |
| Name conflicts | Detect existing symbols with same name |
| Visibility changes | Warn if pub/private scope changes |
| Macro-generated code | Warn about code in macros |
| Documentation | Flag doc comments mentioning symbol |
| Test coverage | Show affected tests |
## Dry Run Mode
Always use `--dry-run` first to preview changes:
```
/rust-refactor-helper rename old_name new_name --dry-run
```
This shows all changes without applying them.
## Related Skills
| When | See |
|------|-----|
| Navigate to symbol | rust-code-navigator |
| Understand call flow | rust-call-graph |
| Project structure | rust-symbol-analyzer |
| Trait implementations | rust-trait-explorer |

239
skills/rust-router/SKILL.md Normal file
View File

@@ -0,0 +1,239 @@
---
name: rust-router
description: "CRITICAL: Use for ALL Rust questions including errors, design, and coding.
HIGHEST PRIORITY for: 比较, 对比, compare, vs, versus, 区别, difference, 最佳实践, best practice,
tokio vs, async-std vs, 比较 tokio, 比较 async,
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, 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"
globs: ["**/Cargo.toml", "**/*.rs"]
---
---
# Rust Question Router
> **Version:** 2.0.0 | **Last Updated:** 2025-01-22
>
> **v2.0:** Context optimized - detailed examples moved to sub-files
## 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** |
---
## INSTRUCTIONS FOR CLAUDE
### CRITICAL: Negotiation Protocol Trigger
**BEFORE answering, check if negotiation is required:**
| Query Contains | Action |
|----------------|--------|
| "比较", "对比", "compare", "vs", "versus" | **MUST use negotiation** |
| "最佳实践", "best practice" | **MUST use negotiation** |
| Domain + error (e.g., "交易系统 E0382") | **MUST use negotiation** |
| Ambiguous scope (e.g., "tokio 性能") | **SHOULD use negotiation** |
**When negotiation is required, include:**
```markdown
## Negotiation Analysis
**Query Type:** [Comparative | Cross-domain | Synthesis | Ambiguous]
**Negotiation:** Enabled
### Source: [Agent/Skill Name]
**Confidence:** HIGH | MEDIUM | LOW | UNCERTAIN
**Gaps:** [What's missing]
## Synthesized Answer
[Answer]
**Overall Confidence:** [Level]
**Disclosed Gaps:** [Gaps user should know]
```
> **详细协议见:** `patterns/negotiation.md`
---
### Default Project Settings
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"
```
---
## Layer 1 Skills (Language Mechanics)
| Pattern | Route To |
|---------|----------|
| move, borrow, lifetime, E0382, E0597 | m01-ownership |
| Box, Rc, Arc, RefCell, Cell | m02-resource |
| mut, interior mutability, E0499, E0502, E0596 | m03-mutability |
| generic, trait, inline, monomorphization | m04-zero-cost |
| type state, phantom, newtype | m05-type-driven |
| Result, Error, panic, ?, anyhow, thiserror | m06-error-handling |
| Send, Sync, thread, async, channel | m07-concurrency |
| unsafe, FFI, extern, raw pointer, transmute | **unsafe-checker** |
## Layer 2 Skills (Design Choices)
| Pattern | Route To |
|---------|----------|
| domain model, business logic | m09-domain |
| performance, optimization, benchmark | m10-performance |
| integration, interop, bindings | m11-ecosystem |
| resource lifecycle, RAII, Drop | m12-lifecycle |
| domain error, recovery strategy | m13-domain-error |
| mental model, how to think | m14-mental-model |
| anti-pattern, common mistake, pitfall | 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 | Route To | Common Cause |
|------------|----------|--------------|
| E0382 | m01-ownership | Use of moved value |
| E0597 | m01-ownership | Lifetime too short |
| E0506 | m01-ownership | Cannot assign to borrowed |
| E0507 | m01-ownership | Cannot move out of borrowed |
| E0515 | m01-ownership | Return local reference |
| E0716 | m01-ownership | Temporary value dropped |
| E0106 | m01-ownership | Missing lifetime specifier |
| E0596 | m03-mutability | Cannot borrow as mutable |
| E0499 | m03-mutability | Multiple mutable borrows |
| E0502 | m03-mutability | Borrow conflict |
| E0277 | m04/m07 | Trait bound not satisfied |
| E0308 | m04-zero-cost | Type mismatch |
| E0599 | m04-zero-cost | No method found |
| E0038 | m04-zero-cost | Trait not object-safe |
| E0433 | m11-ecosystem | Cannot find crate/module |
---
## Functional Routing Table
| Pattern | Route To | Action |
|---------|----------|--------|
| latest version, what's new | **rust-learner** | Use agents |
| API, docs, documentation | **docs-researcher** | Use agent |
| code style, naming, clippy | **coding-guidelines** | Read skill |
| unsafe code, FFI | **unsafe-checker** | Read skill |
| code review | **os-checker** | See `integrations/os-checker.md` |
---
## 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**
### Keyword Conflict Resolution
| Keyword | Resolution |
|---------|------------|
| `unsafe` | **unsafe-checker** (more specific than m11) |
| `error` | **m06** for general, **m13** for domain-specific |
| `RAII` | **m12** for design, **m01** for implementation |
| `crate` | **rust-learner** for version, **m11** for integration |
| `tokio` | **tokio-*** for API, **m07** for concepts |
**Priority Hierarchy:**
```
1. Error codes (E0xxx) → Direct lookup, highest priority
2. Negotiation triggers (compare, vs, best practice) → Enable negotiation
3. Domain keywords + error → Load BOTH domain + error skills
4. Specific crate keywords → Route to crate-specific skill if exists
5. General concept keywords → Route to meta-question skill
```
---
## Sub-Files Reference
| File | Content |
|------|---------|
| `patterns/negotiation.md` | Negotiation protocol details |
| `examples/workflow.md` | Workflow examples |
| `integrations/os-checker.md` | OS-Checker integration |

View File

@@ -0,0 +1,85 @@
# Workflow Examples
> rust-router 的工作流程示例
## Example 1: Error Code with Domain Context
```
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)"
```
## Example 2: Design Question
```
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
```
## Example 3: Comparative Query
```
User: "Compare tokio and async-std"
1. Detect: "compare" → Enable negotiation
2. Load both runtime knowledge sources
3. Assess confidence for each
4. Synthesize with disclosed gaps
5. Answer: Structured comparison table
```
## Example 4: Multi-Layer Trace
```
User: "My web API reports Rc cannot be sent between threads"
1. Entry: Layer 1 (Send/Sync error)
2. Load: m07-concurrency
3. Detect: "web API" → domain-web
4. Dual-skill loading:
- m07: Explain Send/Sync bounds
- domain-web: Web state management patterns
5. Answer: Use Arc instead of Rc, or move to thread-local
```
## Example 5: Intent Analysis Request
```
User: "Analyze this question: How do I share state in actix-web?"
Analysis Steps:
1. Extract Keywords: share, state, actix-web
2. Identify Entry Layer: Layer 1 (sharing = concurrency) + Layer 3 (actix-web = web)
3. Map to Skills: m07-concurrency, domain-web
4. Report:
- Layer 1: Concurrency (state sharing mechanisms)
- Layer 3: Web domain (HTTP handler patterns)
- Suggested trace: L1 → L3
5. Invoke: m07-concurrency first, then domain-web
```

View File

@@ -0,0 +1,56 @@
# OS-Checker Integration
> 代码审查和安全审计工具集成
## Available Commands
| 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 |
## When to Suggest OS-Checker
| User Intent | Suggest |
|-------------|---------|
| Code review request | `/rust-review` |
| Security concerns | `/audit security` |
| Unsafe code review | `/audit safety` |
| Deadlock/race concerns | `/audit concurrency` |
| Pre-release check | `/audit full` |
## Tool Descriptions
### clippy
Standard Rust linter for code style and common mistakes.
### cargo audit
Security vulnerability scanner for dependencies.
### geiger
Counts unsafe code usage in dependencies.
### miri
Interprets MIR to detect undefined behavior.
### rudra
Memory safety bug detector.
### lockbud
Deadlock and concurrency bug detector.
## Integration Flow
```
User: "Review my unsafe code"
Router detects: unsafe + review
├── Load: unsafe-checker skill (for manual review)
└── Suggest: `/audit safety` (for automated check)
```

View File

@@ -0,0 +1,154 @@
# Negotiation Protocol
> 比较查询和跨领域问题的处理协议
## When to Enable Negotiation
For complex queries requiring structured agent responses, enable negotiation mode.
| Query Pattern | Enable Negotiation | Reason |
|---------------|-------------------|--------|
| Single error code lookup | No | Direct answer |
| Single crate version | No | Direct lookup |
| "Compare X and Y" | **Yes** | Multi-faceted |
| Domain + error | **Yes** | Cross-layer context |
| "Best practices for..." | **Yes** | Requires synthesis |
| Ambiguous scope | **Yes** | Needs clarification |
| Multi-crate question | **Yes** | Multiple sources |
## Negotiation Decision Flow
```
Query Received
┌─────────────────────────────┐
│ Is query single-lookup? │
│ (version, error code, def) │
└─────────────────────────────┘
├── Yes → Direct dispatch (no negotiation)
▼ No
┌─────────────────────────────┐
│ Does query require: │
│ - Comparison? │
│ - Cross-domain context? │
│ - Synthesis/aggregation? │
│ - Multiple sources? │
└─────────────────────────────┘
├── Yes → Dispatch with negotiation: true
▼ No
┌─────────────────────────────┐
│ Is scope ambiguous? │
└─────────────────────────────┘
├── Yes → Dispatch with negotiation: true
▼ No
└── Direct dispatch (no negotiation)
```
## Negotiation Dispatch
When dispatching with negotiation:
```
1. Set `negotiation: true`
2. Include original query context
3. Expect structured response:
- Findings
- Confidence (HIGH/MEDIUM/LOW/UNCERTAIN)
- Gaps identified
- Context questions (if any)
4. Evaluate response against original intent
```
## Orchestrator Evaluation
After receiving negotiation response:
| Confidence | Intent Coverage | Action |
|------------|-----------------|--------|
| HIGH | Complete | Synthesize answer |
| HIGH | Partial | May need supplementary query |
| MEDIUM | Complete | Accept with disclosed gaps |
| MEDIUM | Partial | Refine with context |
| LOW | Any | Refine or try alternative |
| UNCERTAIN | Any | Try alternative or escalate |
## Refinement Loop
If response insufficient:
```
Round 1: Initial query
▼ (LOW confidence or gaps block intent)
Round 2: Refined query with:
- Answers to agent's context questions
- Narrowed scope
▼ (still insufficient)
Round 3: Final attempt with:
- Alternative agent/source
- Maximum context provided
▼ (still insufficient)
Synthesize best-effort answer with disclosed gaps
```
## Integration with 3-Strike Rule
Negotiation follows the 3-Strike escalation:
```
Strike 1: Initial query returns LOW confidence
→ Refine with more context
Strike 2: Refined query still LOW
→ Try alternative agent/source
Strike 3: Still insufficient
→ Synthesize best-effort answer
→ Report gaps to user explicitly
```
See `_meta/error-protocol.md` for full escalation rules.
## Negotiation Routing Examples
**Example 1: No Negotiation Needed**
```
Query: "What is tokio's latest version?"
Analysis: Single lookup
Action: Direct dispatch to crate-researcher
```
**Example 2: Negotiation Required**
```
Query: "Compare tokio and async-std for a web server"
Analysis: Comparative + domain context
Action: Dispatch with negotiation: true
Expected: Structured responses from both runtime lookups
Evaluation: Check if web-server specific data found
```
**Example 3: Cross-Domain Negotiation**
```
Query: "E0382 in my trading system"
Analysis: Error code + domain context
Action:
- Dispatch m01-ownership (standard - error is defined)
- Dispatch domain-fintech (negotiation: true - domain context)
Synthesis: Combine error explanation with domain-appropriate fix
```
## Related Documents
- `_meta/negotiation-protocol.md` - Full protocol specification
- `_meta/negotiation-templates.md` - Response templates
- `_meta/error-protocol.md` - 3-Strike escalation
- `agents/_negotiation/response-format.md` - Agent response format

View File

@@ -0,0 +1,265 @@
---
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"
argument-hint: "<crate_name|std::module>"
context: fork
agent: general-purpose
---
# Rust Skill Creator
> **Version:** 2.1.0 | **Last Updated:** 2025-01-27
>
> 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
## Execution Mode Detection
**CRITICAL: Check if related commands/skills are available.**
This skill relies on:
- `/create-llms-for-skills` command
- `/create-skills-via-llms` command
---
## Agent Mode (Plugin Install)
**When the commands above are available (full plugin installation):**
### 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]
```
---
## Inline Mode (Skills-only Install)
**When the commands above are NOT available, create skills manually:**
### Step 1: Identify Target and Construct URL
| 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` |
### Step 2: Fetch Documentation
```bash
# Using agent-browser CLI
agent-browser open "<documentation_url>"
agent-browser get text ".docblock"
agent-browser close
```
**Or with WebFetch fallback:**
```
WebFetch("<documentation_url>", "Extract API documentation including types, functions, and examples")
```
### Step 3: Create Skill Directory
```bash
mkdir -p ~/.claude/skills/{crate_name}
mkdir -p ~/.claude/skills/{crate_name}/references
```
### Step 4: Generate SKILL.md
Create `~/.claude/skills/{crate_name}/SKILL.md` with this template:
```markdown
---
name: {crate_name}
description: "Documentation for {crate_name} crate. Keywords: {keywords}"
---
# {Crate Name}
> **Version:** {version} | **Source:** docs.rs
## Overview
{Brief description from documentation}
## Key Types
### {Type1}
{Description and usage}
### {Type2}
{Description and usage}
## Common Patterns
{Usage patterns extracted from documentation}
## Examples
```rust
{Example code from documentation}
```
## Documentation
- `./references/overview.md` - Main overview
- `./references/{module}.md` - Module documentation
## Links
- [docs.rs](https://docs.rs/{crate})
- [crates.io](https://crates.io/crates/{crate})
```
### Step 5: Generate Reference Files
For each major module or type, create a reference file:
```bash
# Fetch and save module documentation
agent-browser open "https://docs.rs/{crate}/latest/{crate}/{module}/"
agent-browser get text ".docblock" > ~/.claude/skills/{crate_name}/references/{module}.md
agent-browser close
```
### Step 6: Verify Skill
```bash
# Check skill structure
ls -la ~/.claude/skills/{crate_name}/
cat ~/.claude/skills/{crate_name}/SKILL.md
```
---
## 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 (Agent Mode)
```
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 Crate Skill (Inline Mode)
```
User: "Create a dynamic skill for tokio"
Claude:
1. Identify: Third-party crate "tokio"
2. Fetch: agent-browser open "https://docs.rs/tokio/latest/tokio/"
3. Extract documentation
4. Create: ~/.claude/skills/tokio/SKILL.md
5. Create: ~/.claude/skills/tokio/references/
6. Save reference files for key modules (sync, task, runtime, etc.)
```
### Example 3: Create Std Library Skill
```
User: "Create a skill for Send and Sync traits"
Claude:
1. Identify: Std library traits
2. (Agent Mode) 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
(Inline Mode) Fetch each URL, create skill manually
3. Complete skill creation
```
---
## DO NOT
- Use `best-skill-creator` for Rust-related skill creation
- Guess documentation URLs without verification
- Skip documentation fetching step
## Output Location
All generated skills are saved to: `~/.claude/skills/`
## Error Handling
| Error | Cause | Solution |
|-------|-------|----------|
| Commands not found | Skills-only install | Use inline mode |
| URL not found | Invalid crate/module | Verify crate exists on crates.io |
| Empty documentation | API changed | Use alternative selectors |
| Permission denied | Directory issue | Check ~/.claude/skills/ permissions |

View File

@@ -0,0 +1,222 @@
---
name: rust-symbol-analyzer
description: "Analyze Rust project structure using LSP symbols. Triggers on: /symbols, project structure, list structs, list traits, list functions, 符号分析, 项目结构, 列出所有, 有哪些struct"
argument-hint: "[file.rs] [--type struct|trait|fn|mod]"
allowed-tools: ["LSP", "Read", "Glob"]
---
# Rust Symbol Analyzer
Analyze project structure by examining symbols across your Rust codebase.
## Usage
```
/rust-symbol-analyzer [file.rs] [--type struct|trait|fn|mod]
```
**Examples:**
- `/rust-symbol-analyzer` - Analyze entire project
- `/rust-symbol-analyzer src/lib.rs` - Analyze single file
- `/rust-symbol-analyzer --type trait` - List all traits in project
## LSP Operations
### 1. Document Symbols (Single File)
Get all symbols in a file with their hierarchy.
```
LSP(
operation: "documentSymbol",
filePath: "src/lib.rs",
line: 1,
character: 1
)
```
**Returns:** Nested structure of modules, structs, functions, etc.
### 2. Workspace Symbols (Entire Project)
Search for symbols across the workspace.
```
LSP(
operation: "workspaceSymbol",
filePath: "src/lib.rs",
line: 1,
character: 1
)
```
**Note:** Query is implicit in the operation context.
## Workflow
```
User: "What's the structure of this project?"
[1] Find all Rust files
Glob("**/*.rs")
[2] Get symbols from each key file
LSP(documentSymbol) for lib.rs, main.rs
[3] Categorize by type
[4] Generate structure visualization
```
## Output Format
### Project Overview
```
## Project Structure: my-project
### Modules
├── src/
│ ├── lib.rs (root)
│ ├── config/
│ │ ├── mod.rs
│ │ └── parser.rs
│ ├── handlers/
│ │ ├── mod.rs
│ │ ├── auth.rs
│ │ └── api.rs
│ └── models/
│ ├── mod.rs
│ ├── user.rs
│ └── order.rs
└── tests/
└── integration.rs
```
### By Symbol Type
```
## Symbols by Type
### Structs (12)
| Name | Location | Fields | Derives |
|------|----------|--------|---------|
| Config | src/config.rs:10 | 5 | Debug, Clone |
| User | src/models/user.rs:8 | 4 | Debug, Serialize |
| Order | src/models/order.rs:15 | 6 | Debug, Serialize |
| ... | | | |
### Traits (4)
| Name | Location | Methods | Implementors |
|------|----------|---------|--------------|
| Handler | src/handlers/mod.rs:5 | 3 | AuthHandler, ApiHandler |
| Repository | src/db/mod.rs:12 | 5 | UserRepo, OrderRepo |
| ... | | | |
### Functions (25)
| Name | Location | Visibility | Async |
|------|----------|------------|-------|
| main | src/main.rs:10 | pub | yes |
| parse_config | src/config.rs:45 | pub | no |
| ... | | | |
### Enums (6)
| Name | Location | Variants |
|------|----------|----------|
| Error | src/error.rs:5 | 8 |
| Status | src/models/order.rs:5 | 4 |
| ... | | |
```
### Single File Analysis
```
## src/handlers/auth.rs
### Symbols Hierarchy
mod auth
├── struct AuthHandler
│ ├── field: config: Config
│ ├── field: db: Pool
│ └── impl AuthHandler
│ ├── fn new(config, db) -> Self
│ ├── fn authenticate(&self, token) -> Result<User>
│ └── fn refresh_token(&self, user) -> Result<Token>
├── struct Token
│ ├── field: value: String
│ └── field: expires: DateTime
├── enum AuthError
│ ├── InvalidToken
│ ├── Expired
│ └── Unauthorized
└── impl Handler for AuthHandler
├── fn handle(&self, req) -> Response
└── fn name(&self) -> &str
```
## Analysis Features
### Complexity Metrics
```
## Complexity Analysis
| File | Structs | Functions | Lines | Complexity |
|------|---------|-----------|-------|------------|
| src/handlers/auth.rs | 2 | 8 | 150 | Medium |
| src/models/user.rs | 3 | 12 | 200 | High |
| src/config.rs | 1 | 3 | 50 | Low |
**Hotspots:** Files with high complexity that may need refactoring
- src/handlers/api.rs (15 functions, 300 lines)
```
### Dependency Analysis
```
## Internal Dependencies
auth.rs
├── imports from: config.rs, models/user.rs, db/mod.rs
└── imported by: main.rs, handlers/mod.rs
user.rs
├── imports from: (none - leaf module)
└── imported by: auth.rs, api.rs, tests/
```
## Symbol Types
| Type | Icon | LSP Kind |
|------|------|----------|
| Module | 📦 | Module |
| Struct | 🏗️ | Struct |
| Enum | 🔢 | Enum |
| Trait | 📜 | Interface |
| Function | ⚡ | Function |
| Method | 🔧 | Method |
| Constant | 🔒 | Constant |
| Field | 📎 | Field |
## Common Queries
| User Says | Analysis |
|-----------|----------|
| "What structs are in this project?" | workspaceSymbol + filter |
| "Show me src/lib.rs structure" | documentSymbol |
| "Find all async functions" | workspaceSymbol + async filter |
| "List public API" | documentSymbol + pub filter |
## Related Skills
| When | See |
|------|-----|
| Navigate to symbol | rust-code-navigator |
| Call relationships | rust-call-graph |
| Trait implementations | rust-trait-explorer |
| Safe refactoring | rust-refactor-helper |

View File

@@ -0,0 +1,248 @@
---
name: rust-trait-explorer
description: "Explore Rust trait implementations using LSP. Triggers on: /trait-impl, find implementations, who implements, trait 实现, 谁实现了, 实现了哪些trait"
argument-hint: "<TraitName|StructName>"
allowed-tools: ["LSP", "Read", "Glob", "Grep"]
---
# Rust Trait Explorer
Discover trait implementations and understand polymorphic designs.
## Usage
```
/rust-trait-explorer <TraitName|StructName>
```
**Examples:**
- `/rust-trait-explorer Handler` - Find all implementors of Handler trait
- `/rust-trait-explorer MyStruct` - Find all traits implemented by MyStruct
## LSP Operations
### Go to Implementation
Find all implementations of a trait.
```
LSP(
operation: "goToImplementation",
filePath: "src/traits.rs",
line: 10,
character: 11
)
```
**Use when:**
- Trait name is known
- Want to find all implementors
- Understanding polymorphic code
## Workflow
### Find Trait Implementors
```
User: "Who implements the Handler trait?"
[1] Find trait definition
LSP(goToDefinition) or workspaceSymbol
[2] Get implementations
LSP(goToImplementation)
[3] For each impl, get details
LSP(documentSymbol) for methods
[4] Generate implementation map
```
### Find Traits for a Type
```
User: "What traits does MyStruct implement?"
[1] Find struct definition
[2] Search for "impl * for MyStruct"
Grep pattern matching
[3] Get trait details for each
[4] Generate trait list
```
## Output Format
### Trait Implementors
```
## Implementations of `Handler`
**Trait defined at:** src/traits.rs:15
```rust
pub trait Handler {
fn handle(&self, request: Request) -> Response;
fn name(&self) -> &str;
}
```
### Implementors (4)
| Type | Location | Notes |
|------|----------|-------|
| AuthHandler | src/handlers/auth.rs:20 | Handles authentication |
| ApiHandler | src/handlers/api.rs:15 | REST API endpoints |
| WebSocketHandler | src/handlers/ws.rs:10 | WebSocket connections |
| MockHandler | tests/mocks.rs:5 | Test mock |
### Implementation Details
#### AuthHandler
```rust
impl Handler for AuthHandler {
fn handle(&self, request: Request) -> Response {
// Authentication logic
}
fn name(&self) -> &str {
"auth"
}
}
```
#### ApiHandler
```rust
impl Handler for ApiHandler {
fn handle(&self, request: Request) -> Response {
// API routing logic
}
fn name(&self) -> &str {
"api"
}
}
```
```
### Traits for a Type
```
## Traits implemented by `User`
**Struct defined at:** src/models/user.rs:10
### Standard Library Traits
| Trait | Derived/Manual | Notes |
|-------|----------------|-------|
| Debug | #[derive] | Auto-generated |
| Clone | #[derive] | Auto-generated |
| Default | manual | Custom defaults |
| Display | manual | User-friendly output |
### Serde Traits
| Trait | Location |
|-------|----------|
| Serialize | #[derive] |
| Deserialize | #[derive] |
### Project Traits
| Trait | Location | Methods |
|-------|----------|---------|
| Entity | src/db/entity.rs:30 | id(), created_at() |
| Validatable | src/validation.rs:15 | validate() |
### Implementation Hierarchy
```
User
├── derive
│ ├── Debug
│ ├── Clone
│ ├── Serialize
│ └── Deserialize
└── impl
├── Default (src/models/user.rs:50)
├── Display (src/models/user.rs:60)
├── Entity (src/models/user.rs:70)
└── Validatable (src/models/user.rs:85)
```
```
## Trait Hierarchy Visualization
```
## Trait Hierarchy
┌─────────────┐
│ Error │ (std)
└──────┬──────┘
┌────────────┼────────────┐
│ │ │
┌───────▼───────┐ ┌──▼──┐ ┌───────▼───────┐
│ AppError │ │ ... │ │ DbError │
└───────┬───────┘ └─────┘ └───────┬───────┘
│ │
┌───────▼───────┐ ┌───────▼───────┐
│ AuthError │ │ QueryError │
└───────────────┘ └───────────────┘
```
## Analysis Features
### Coverage Check
```
## Trait Implementation Coverage
Trait: Handler (3 required methods)
| Implementor | handle() | name() | priority() | Complete |
|-------------|----------|--------|------------|----------|
| AuthHandler | ✅ | ✅ | ✅ | Yes |
| ApiHandler | ✅ | ✅ | ❌ default | Yes |
| MockHandler | ✅ | ✅ | ✅ | Yes |
```
### Blanket Implementations
```
## Blanket Implementations
The following blanket impls may apply to your types:
| Trait | Blanket Impl | Applies To |
|-------|--------------|------------|
| From<T> | `impl<T> From<T> for T` | All types |
| Into<U> | `impl<T, U> Into<U> for T where U: From<T>` | Types with From |
| ToString | `impl<T: Display> ToString for T` | Types with Display |
```
## Common Patterns
| User Says | Action |
|-----------|--------|
| "Who implements X?" | goToImplementation on trait |
| "What traits does Y impl?" | Grep for `impl * for Y` |
| "Show trait hierarchy" | Find super-traits recursively |
| "Is X: Send + Sync?" | Check std trait impls |
## Related Skills
| When | See |
|------|-----|
| Navigate to impl | rust-code-navigator |
| Call relationships | rust-call-graph |
| Project structure | rust-symbol-analyzer |
| Safe refactoring | rust-refactor-helper |

View File

@@ -0,0 +1,136 @@
# 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

@@ -0,0 +1,86 @@
---
name: unsafe-checker
description: "CRITICAL: Use for unsafe Rust code review and FFI. Triggers on: unsafe, raw pointer, FFI, extern, transmute, *mut, *const, union, #[repr(C)], libc, std::ffi, MaybeUninit, NonNull, SAFETY comment, soundness, undefined behavior, UB, safe wrapper, memory layout, bindgen, cbindgen, CString, CStr, 安全抽象, 裸指针, 外部函数接口, 内存布局, 不安全代码, FFI 绑定, 未定义行为"
globs: ["**/*.rs"]
allowed-tools: ["Read", "Grep", "Glob"]
---
Display the following ASCII art exactly as shown. Do not modify spaces or line breaks:
```text
⚠️ **Unsafe Rust Checker Loaded**
* ^ *
/◉\_~^~_/◉\
⚡/ o \⚡
'_ _'
/ '-----' \
```
---
# 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

@@ -0,0 +1,115 @@
# 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

@@ -0,0 +1,253 @@
# 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

@@ -0,0 +1,113 @@
# 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

@@ -0,0 +1,353 @@
# 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

@@ -0,0 +1,272 @@
# 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

@@ -0,0 +1,17 @@
{
"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

@@ -0,0 +1,77 @@
# 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

@@ -0,0 +1,53 @@
# 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

@@ -0,0 +1,122 @@
---
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

@@ -0,0 +1,133 @@
---
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

@@ -0,0 +1,162 @@
---
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

@@ -0,0 +1,145 @@
---
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

@@ -0,0 +1,113 @@
---
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

@@ -0,0 +1,151 @@
---
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

@@ -0,0 +1,131 @@
---
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

@@ -0,0 +1,146 @@
---
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

@@ -0,0 +1,136 @@
---
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

@@ -0,0 +1,132 @@
---
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

@@ -0,0 +1,142 @@
---
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

@@ -0,0 +1,164 @@
---
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

@@ -0,0 +1,146 @@
---
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

@@ -0,0 +1,135 @@
---
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

@@ -0,0 +1,145 @@
---
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

@@ -0,0 +1,141 @@
---
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

@@ -0,0 +1,152 @@
---
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

@@ -0,0 +1,165 @@
---
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

@@ -0,0 +1,71 @@
---
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

@@ -0,0 +1,90 @@
---
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

@@ -0,0 +1,74 @@
---
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

@@ -0,0 +1,151 @@
---
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

@@ -0,0 +1,130 @@
---
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

@@ -0,0 +1,113 @@
---
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

@@ -0,0 +1,127 @@
---
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

@@ -0,0 +1,121 @@
---
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

@@ -0,0 +1,147 @@
---
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

@@ -0,0 +1,146 @@
---
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

@@ -0,0 +1,113 @@
---
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

@@ -0,0 +1,114 @@
---
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

@@ -0,0 +1,125 @@
---
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

@@ -0,0 +1,117 @@
---
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

@@ -0,0 +1,120 @@
---
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

@@ -0,0 +1,110 @@
---
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

@@ -0,0 +1,113 @@
---
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

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