agentleFS
adxptivedadxptived/Rust-Skills/skills/rust-mastery/SKILL.md

rust-mastery

Skill1 starsChanged 6 months agoMITView on GitHub

Comprehensive Rust programming skill covering ownership, borrowing, lifetimes, traits, error handling, and idiomatic patterns. ALWAYS use this skill when: - User writes, reads, or debugs ANY Rust code (.rs files) - User encounters borrow checker errors like "cannot borrow as mutable", "value moved here", "missing lifetime specifier" - User asks about ownership, borrowing, references, lifetimes, or the borrow checker - User works with Result, Option, ? operator, match expressions, or enums - User asks about traits, generics, impl blocks, derive macros, or trait bounds - User wants to understand Rust idioms or "the Rust way" to do something - User is converting code from another language to Rust - User asks about Vec, String, &str, HashMap, iterators, or standard library types Even if the user doesn't explicitly say "Rust", trigger this skill for any .rs file or Rust-related compilation error.

The file

We split this skill into the rules it gives an agent

11 rules. About 0 are common, 9 are distinctive, and 2 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈9

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 2

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Read additional references based on the task:

    line 22
  2. Distinctive:

    Express invariants at compile time: - If a state shouldn't exist, make it unrepresentable

    line 33
  3. Distinctive:

    Encode business logic in types: - Use newtypes, enums, and marker types

    line 34
  4. Distinctive:

    Every value in Rust has exactly one owner.

    line 58
  5. Distinctive:

    When the owner goes out of scope, the value is dropped

    line 62
  6. Very specific:

    Avoid explicit match when transforms work.

    line 102
  7. Distinctive:

    Prefer iterator transforms over explicit loops.

    line 174
  8. Distinctive:

    Prefer borrowing in function parameters unless ownership is required.

    line 411
  9. Very specific:

    Use Result for expected failures and reserve panics for bugs/invariants.

    line 413
  10. Distinctive:

    Keep lifetimes tied to real borrowed data.

    line 415
  11. Distinctive:

    Prefer iterator adapters when they clarify ownership and control flow.

    line 416

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Rust Mastery

Comprehensive guide to writing idiomatic, safe, and performant Rust code. This skill synthesizes best practices from "The Rust Programming Language", "Programming Rust", "Effective Rust", and community patterns.

Quick Navigation

Read additional references based on the task:

  • references/ownership.md - Deep dive into ownership, borrowing, and lifetimes
  • references/traits.md - Trait design, standard traits, and generics
  • references/error-handling.md - Result, Option, and error handling patterns
  • references/idioms.md - Common Rust idioms and patterns

Core Principles

1. Embrace the Type System

Rust's type system is your ally. Use it to:

  • Express invariants at compile time - If a state shouldn't exist, make it unrepresentable
  • Encode business logic in types - Use newtypes, enums, and marker types
  • Leverage zero-cost abstractions - Generics and traits have no runtime overhead
// Bad: String that could be anything
fn process_email(email: String) { ... }

// Good: Newtype enforces validation
struct Email(String);
impl Email {
    pub fn new(s: &str) -> Result<Self, EmailError> {
        // Validation happens once, at creation
        if s.contains('@') && s.contains('.') {
            Ok(Self(s.to_string()))
        } else {
            Err(EmailError::Invalid)
        }
    }
}
fn process_email(email: Email) { ... } // Can only receive valid emails

2. Ownership Fundamentals

Every value in Rust has exactly one owner. When the owner goes out of scope, the value is dropped.

Three rules to internalize:

  1. Each value has exactly one owner
  2. When the owner goes out of scope, the value is dropped
  3. Ownership can be transferred (moved) or borrowed (referenced)
// Move semantics (ownership transfer)
let s1 = String::from("hello");
let s2 = s1; // s1 is moved, no longer valid
// println!("{}", s1); // Compile error!

// Borrowing (reference without taking ownership)
let s1 = String::from("hello");
let len = calculate_length(&s1); // Borrow s1
println!("{} has length {}", s1, len); // s1 still valid

fn calculate_length(s: &str) -> usize {
    s.len()
}

3. Borrowing Rules

The borrow checker enforces these rules at compile time:

  • You can have either ONE mutable reference OR any number of immutable references
  • References must always be valid (no dangling references)
let mut data = vec![1, 2, 3];

// Multiple immutable borrows OK
let r1 = &data;
let r2 = &data;
println!("{:?} {:?}", r1, r2);

// Mutable borrow after immutable borrows end
let r3 = &mut data;
r3.push(4);

4. Prefer Option and Result Transforms

Avoid explicit match when transforms work. This produces cleaner, more composable code.

// Verbose match-based approach
fn get_user_email(user_id: u32) -> Option<String> {
    match find_user(user_id) {
        Some(user) => match user.email {
            Some(email) => Some(email.to_lowercase()),
            None => None,
        },
        None => None,
    }
}

// Idiomatic transform approach
fn get_user_email(user_id: u32) -> Option<String> {
    find_user(user_id)
        .and_then(|user| user.email)
        .map(|email| email.to_lowercase())
}

// With the ? operator for Results
fn get_user_data(id: u32) -> Result<UserData, Error> {
    let user = find_user(id)?;
    let profile = fetch_profile(&user)?;
    let settings = load_settings(&user)?;
    Ok(UserData { user, profile, settings })
}

5. Error Handling Philosophy

Rust distinguishes between recoverable errors (Result) and unrecoverable errors (panic!).

Use Result for:

  • File operations, network calls, parsing
  • Any operation that might legitimately fail
  • Library code (let the caller decide how to handle)

Use panic for:

  • Programming bugs (index out of bounds, unwrap on None)
  • Unrecoverable states
  • Tests and examples
// Define custom error types
use thiserror::Error;

#[derive(Error, Debug)]
pub enum DataError {
    #[error("Failed to parse data: {0}")]
    ParseError(String),
    #[error("IO error: {0}")]
    IoError(#[from] std::io::Error),
    #[error("Not found: {0}")]
    NotFound(String),
}

// Use anyhow for application code
use anyhow::{Context, Result};

fn process_config() -> Result<Config> {
    let content = std::fs::read_to_string("config.toml")
        .context("Failed to read config file")?;
    let config: Config = toml::from_str(&content)
        .context("Failed to parse config")?;
    Ok(config)
}

6. Iterator Patterns

Prefer iterator transforms over explicit loops. They're often more readable and equally performant.

// Imperative style
let mut results = Vec::new();
for item in items {
    if item.is_valid() {
        results.push(item.transform());
    }
}

// Functional style (preferred)
let results: Vec<_> = items
    .iter()
    .filter(|item| item.is_valid())
    .map(|item| item.transform())
    .collect();

// Useful iterator methods
items.iter().find(|x| x.id == target_id)     // First match
items.iter().any(|x| x.is_active())          // Existence check
items.iter().all(|x| x.is_valid())           // Universal check
items.iter().fold(0, |acc, x| acc + x.value) // Reduce/accumulate
items.iter().flat_map(|x| x.children())      // Flatten nested
items.iter().enumerate()                      // Index + value
items.iter().zip(other.iter())               // Pair two iterators

7. Struct and Enum Design

Structs for data with named fields:

struct User {
    id: UserId,
    name: String,
    email: Email,
    created_at: DateTime<Utc>,
}

Enums for variants/states:

enum ConnectionState {
    Disconnected,
    Connecting { attempt: u32 },
    Connected { session_id: String },
    Error { message: String, retryable: bool },
}

// Pattern matching extracts data
match state {
    ConnectionState::Connected { session_id } => {
        println!("Session: {}", session_id);
    }
    ConnectionState::Error { message, retryable: true } => {
        println!("Retryable error: {}", message);
    }
    _ => {}
}

8. Common Patterns

Builder Pattern for complex construction:

#[derive(Default)]
struct RequestBuilder {
    url: Option<String>,
    method: Method,
    headers: HashMap<String, String>,
    timeout: Duration,
}

impl RequestBuilder {
    pub fn new() -> Self {
        Self::default()
    }
    
    pub fn url(mut self, url: impl Into<String>) -> Self {
        self.url = Some(url.into());
        self
    }
    
    pub fn header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
        self.headers.insert(key.into(), value.into());
        self
    }
    
    pub fn build(self) -> Result<Request, BuildError> {
        let url = self.url.ok_or(BuildError::MissingUrl)?;
        Ok(Request { url, method: self.method, headers: self.headers, timeout: self.timeout })
    }
}

// Usage
let request = RequestBuilder::new()
    .url("https://api.example.com")
    .header("Authorization", "Bearer token")
    .build()?;

Newtype Pattern for type safety:

struct UserId(u64);
struct OrderId(u64);

// These cannot be confused, even though both are u64
fn get_user_orders(user_id: UserId) -> Vec<OrderId> { ... }

RAII with Drop:

struct TempFile {
    path: PathBuf,
}

impl Drop for TempFile {
    fn drop(&mut self) {
        let _ = std::fs::remove_file(&self.path);
    }
}
// File is automatically deleted when TempFile goes out of scope

9. Performance Guidelines

  • Clone judiciously - Cloning is explicit in Rust; use &str instead of String where possible
  • Use Cow<str> - For functions that might or might not need to allocate
  • Prefer &[T] - Over &Vec<T> in function parameters
  • Use impl Trait - For returning iterators without boxing
use std::borrow::Cow;

// Accepts both owned and borrowed strings efficiently
fn process(input: Cow<'_, str>) -> String {
    if input.contains("special") {
        input.to_uppercase() // Only allocates when needed
    } else {
        input.into_owned()
    }
}

// Flexible slice parameter
fn sum(numbers: &[i32]) -> i32 {
    numbers.iter().sum()
}
// Works with Vec, array, or any contiguous sequence
sum(&vec![1, 2, 3]);
sum(&[1, 2, 3]);

10. Common Compilation Errors

E0382: Use of moved value

// Error: s moved to s2, then used
let s = String::from("hello");
let s2 = s;
println!("{}", s); // Error!

// Fix: Clone if you need both, or use references
let s = String::from("hello");
let s2 = s.clone();
println!("{} {}", s, s2);

E0502: Cannot borrow as mutable because also borrowed as immutable

// Error: Immutable and mutable borrow overlap
let mut v = vec![1, 2, 3];
let first = &v[0];
v.push(4); // Error: v borrowed mutably while first exists
println!("{}", first);

// Fix: End immutable borrow before mutable borrow
let mut v = vec![1, 2, 3];
let first = v[0]; // Copy the value, no borrow
v.push(4);
println!("{}", first);

E0106: Missing lifetime specifier

// Error: Rust can't infer lifetime
fn longest(x: &str, y: &str) -> &str { // Error!
    if x.len() > y.len() { x } else { y }
}

// Fix: Add lifetime annotation
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}

Standard Traits to Know

TraitPurposeWhen to Implement
DebugDebug formatting ({:?})Always (use #[derive(Debug)])
CloneExplicit duplicationWhen copying makes sense
CopyImplicit copy (bitwise)Small, stack-only types
DefaultDefault valueWhen there's a sensible default
PartialEq/EqEquality comparisonComparable types
PartialOrd/OrdOrderingSortable types
HashHashingHashMap/HashSet keys
DisplayUser-facing formatPublic types
From/IntoType conversionWhen conversion is natural
AsRef/AsMutCheap reference conversionFlexible APIs
DerefSmart pointer behaviorWrapper types
IteratorIterationCustom collections
DropCleanup on scope exitRAII resources

Crates to Know

CategoryCratePurpose
Error HandlingthiserrorDerive Error for library types
Error HandlinganyhowFlexible errors for applications
SerializationserdeSerialize/deserialize anything
Async RuntimetokioAsync runtime with full features
HTTP ClientreqwestErgonomic HTTP client
CLIclapCommand-line argument parsing
LoggingtracingStructured logging/tracing
TestingproptestProperty-based testing
TestingcriterionBenchmarking

Quick Reference

See references/ for deep dives:

  • ownership.md - Ownership, borrowing, lifetimes in depth
  • traits.md - Trait system, generics, trait objects
  • error-handling.md - Complete error handling guide
  • idioms.md - Idiomatic patterns and anti-patterns

Review Checklist

  • Prefer borrowing in function parameters unless ownership is required.
  • Make invalid states unrepresentable with enums, newtypes, or typed builders.
  • Use Result for expected failures and reserve panics for bugs/invariants.
  • Derive standard traits where they improve debugging, testing, or ergonomics.
  • Keep lifetimes tied to real borrowed data; do not add annotations as decoration.
  • Prefer iterator adapters when they clarify ownership and control flow.

References

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.