Rust Fundamentals
0
Upvotes
5
Views
0
Downloads
951
Words
Description
Guides Rust code for autonomous agents: ownership, borrowing, lifetimes, error handling, traits, generics, and cargo patterns; use to write safe, idiomatic Rust.
When to Use
Explain Rust ownership and borrowing | Show Rust lifetime best practices | Review error handling with Result and Option | Refactor code to satisfy the borrow checker
Use Cases
Refactor code to satisfy the borrow checker. | Design safe APIs using &T and &mut T. | Implement error handling with Result and Option. | Decide between generics and trait objects. | Organize modules and cargo workspaces for large projects.
SKILL.md Content
---
name: rust-fundamentals
description: "Guides Rust code for autonomous agents: ownership, borrowing, lifetimes, error handling, traits, generics, and cargo patterns; use to write safe, idiomatic Rust."
metadata:
tags: "rust, ownership, borrowing, lifetimes, error-handling, traits-generics, cargo-workspaces"
source: "https://skilldb.dev/skills/autonomous-agent-skills/rust-fundamentals"
pack: "autonomous-agent-skills"
category: "Autonomous Agents"
---
# Rust Fundamentals
## When to use this skill
Use when the user says things like:
- "Explain Rust ownership and borrowing"
- "Show Rust lifetime best practices"
- "Review error handling with Result and Option"
- "Refactor code to satisfy the borrow checker"
You are an autonomous agent that reads, writes, and modifies Rust code. Your role is to work with Rust's ownership model rather than against it, produce code that compiles without fighting the borrow checker, and follow Rust community idioms for error handling, traits, and module organization.
## Philosophy
Rust's strictness is a feature. The borrow checker and type system eliminate entire classes of bugs at compile time — data races, null pointer dereferences, use-after-free. Embrace the constraints. When the compiler rejects your code, it is usually pointing at a real design problem. Restructure the code to satisfy the compiler rather than reaching for `unsafe` or `.clone()` everywhere.
## Techniques
### Ownership and Borrowing
- Every value in Rust has exactly one owner. When the owner goes out of scope, the value is dropped.
- Move semantics apply by default for non-Copy types. After `let b = a;`, `a` is no longer valid.
- Use references (`&T` for shared, `&mut T` for exclusive) to borrow values without taking ownership.
- You can have either one `&mut T` or any number of `&T` references at a time, never both simultaneously.
- Prefer borrowing over cloning. Pass `&str` instead of `String`, `&[T]` instead of `Vec<T>` in function parameters.
- Clone only when you genuinely need an independent copy. Unnecessary clones are a code smell.
### Lifetime Annotations
- Lifetimes tell the compiler how long references are valid. Most are inferred by elision rules.
- Add explicit lifetime annotations when the compiler cannot infer them, typically when a function returns a reference: `fn first<'a>(items: &'a [i32]) -> &'a i32`.
- Use `'_` for anonymous lifetimes when the compiler needs a hint but the name does not matter.
- Avoid `'static` references unless the data truly lives for the entire program duration (string literals, leaked allocations, lazy statics).
- When lifetime annotations grow complex, consider whether the function should return an owned value instead.
### Result and Option Handling
- Use `Result<T, E>` for operations that can fail. Use `Option<T>` for values that may be absent.
- Use the `?` operator to propagate errors concisely: `let data = fs::read_to_string(path)?;`.
- Use combinators for transformations: `.map()`, `.and_then()`, `.unwrap_or()`, `.unwrap_or_else()`.
- Reserve `.unwrap()` and `.expect()` for cases where failure is logically impossible, and document why.
- Never use `.unwrap()` on user input or I/O operations.
### Pattern Matching
- Use `match` for exhaustive handling of enums and `Result`/`Option` types.
- Use `if let` for single-variant matching when you do not care about other variants.
- Use `while let` for consuming iterators or channels pattern by pattern.
- Destructure structs and tuples in match arms for direct field access.
- Use match guards (`if condition`) for additional filtering within arms.
### Traits and Generics
- Define traits for shared behavior: `trait Serialize { fn serialize(&self) -> Vec<u8>; }`.
- Use generic bounds to accept any type implementing required traits: `fn process<T: Display + Debug>(item: T)`.
- Use `impl Trait` in argument position for simple cases: `fn print(item: impl Display)`.
- Use `impl Trait` in return position to hide concrete types: `fn make_iter() -> impl Iterator<Item = i32>`.
- Use `dyn Trait` for dynamic dispatch when you need trait objects (heterogeneous collections, plugin systems).
- Derive common traits when possible: `#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]`.
### Error Handling
- Use `thiserror` for library error types: define an enum with `#[derive(thiserror::Error)]` and `#[error("...")]` messages.
- Use `anyhow` for application-level error handling where you do not need to match on error variants.
- Create error enums that map to the domain: `enum ApiError { NotFound, Unauthorized, Internal(anyhow::Error) }`.
- Implement `From` conversions between error types for smooth `?` usage across module boundaries.
- Add context to errors: `file.read().context("failed to read config file")?` with anyhow.
### Cargo Workspace Management
- Use workspaces for multi-crate projects: define `[workspace]` in the root `Cargo.toml`.
- Share dependencies across workspace members with `[workspace.dependencies]`.
- Split libraries from binaries: `core` crate for logic, `cli` crate for the binary.
- Use `cargo check` for fast compilation feedback during development.
- Run `cargo clippy` for lint warnings and `cargo fmt` for consistent formatting.
### Unsafe Usage Guidelines
- Use `unsafe` only when strictly necessary: FFI, raw pointer manipulation, performance-critical code with proven invariants.
- Document safety invariants in a `// SAFETY:` comment above every `unsafe` block.
- Wrap unsafe code in safe abstractions. The caller should never need to know about the unsafety.
- Prefer safe alternatives: `std::sync` over raw atomics, `Vec` over manual allocation, `Pin` over raw self-referential structures.
### Common Idioms
- Use iterators and combinators instead of manual loops: `.iter().filter().map().collect()`.
- Use `into()` and `from()` for type conversions via the `From`/`Into` traits.
- Use `Default::default()` or `#[derive(Default)]` for sensible zero-values.
- Use builder patterns for complex struct construction.
- Prefer `&str` over `String` in function parameters, `&[T]` over `Vec<T>`.
## Best Practices
- Run `cargo clippy` before committing. Clippy catches hundreds of common mistakes and non-idiomatic patterns.
- Run `cargo fmt` to maintain consistent formatting.
- Write unit tests in the same file with `#[cfg(test)] mod tests`.
- Use `cargo doc --open` to verify documentation renders correctly.
- Enable relevant Clippy lint groups: `#![warn(clippy::pedantic)]` for stricter checks.
## Anti-Patterns
- Calling `.clone()` on every borrow checker error instead of restructuring ownership.
- Using `.unwrap()` liberally in production code without justification.
- Reaching for `unsafe` before exploring safe alternatives.
- Using `String` everywhere instead of `&str` for function parameters.
- Writing `for i in 0..vec.len()` instead of `for item in &vec`.
- Ignoring Clippy warnings or disabling them project-wide.
- Using `Box<dyn Error>` instead of typed error enums or `anyhow::Error`.
- Creating deeply nested `match` expressions instead of using early returns with `?`.