Claude Code Plugins

Community-maintained marketplace

Feedback

Guide for writing Rust code covering ownership, borrowing, lifetimes, error handling, async programming, and Rust best practices

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name rust-language
description Guide for writing Rust code covering ownership, borrowing, lifetimes, error handling, async programming, and Rust best practices

Rust Programming Language

This skill activates when writing Rust code, understanding ownership and borrowing, working with async Rust, or following Rust best practices.

When to Use This Skill

Activate when:

  • Writing Rust code
  • Understanding ownership, borrowing, and lifetimes
  • Implementing error handling with Result and Option
  • Working with traits and generics
  • Writing async/concurrent Rust code
  • Using Cargo and managing dependencies
  • Following Rust idioms and best practices

Ownership and Borrowing

Ownership Rules

Rust's core concept - every value has exactly one owner:

// Ownership transfer (move)
let s1 = String::from("hello");
let s2 = s1;  // s1 is no longer valid
// println!("{}", s1);  // Error!
println!("{}", s2);  // OK

// Copy types (stack-only data)
let x = 5;
let y = x;  // x is still valid (Copy trait)
println!("{} {}", x, y);  // OK

Borrowing

// Immutable borrow
fn calculate_length(s: &String) -> usize {
    s.len()
}

let s = String::from("hello");
let len = calculate_length(&s);
println!("{} has length {}", s, len);  // s still valid

// Mutable borrow
fn append_world(s: &mut String) {
    s.push_str(" world");
}

let mut s = String::from("hello");
append_world(&mut s);
println!("{}", s);  // "hello world"

Borrowing Rules

// Rule 1: Multiple immutable borrows OK
let s = String::from("hello");
let r1 = &s;
let r2 = &s;
println!("{} {}", r1, r2);  // OK

// Rule 2: Only ONE mutable borrow at a time
let mut s = String::from("hello");
let r1 = &mut s;
// let r2 = &mut s;  // Error!
println!("{}", r1);

// Rule 3: Cannot have mutable and immutable borrows together
let mut s = String::from("hello");
let r1 = &s;
// let r2 = &mut s;  // Error!
println!("{}", r1);

Slices

// String slices
let s = String::from("hello world");
let hello = &s[0..5];
let world = &s[6..11];

// Array slices
let arr = [1, 2, 3, 4, 5];
let slice = &arr[1..3];  // [2, 3]

// Function taking slice
fn first_word(s: &str) -> &str {
    let bytes = s.as_bytes();

    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[0..i];
        }
    }

    &s[..]
}

Lifetimes

Lifetime Annotations

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

// Usage
let s1 = String::from("long string");
let s2 = String::from("short");
let result = longest(&s1, &s2);
println!("Longest: {}", result);

Lifetime in Structs

// Struct with lifetime
struct ImportantExcerpt<'a> {
    part: &'a str,
}

impl<'a> ImportantExcerpt<'a> {
    fn announce_and_return(&self) -> &str {
        println!("Attention: {}", self.part);
        self.part
    }
}

// Usage
let novel = String::from("Call me Ishmael. Some years ago...");
let first_sentence = novel.split('.').next().unwrap();
let excerpt = ImportantExcerpt { part: first_sentence };

Lifetime Elision

// Compiler infers lifetimes (no annotation needed)
fn first_word(s: &str) -> &str {
    // Compiler infers: fn first_word<'a>(s: &'a str) -> &'a str
    let bytes = s.as_bytes();
    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[0..i];
        }
    }
    &s[..]
}

Error Handling

Result Type

use std::fs::File;
use std::io::{self, Read};

// Returning Result
fn read_username_from_file() -> Result<String, io::Error> {
    let mut file = File::open("username.txt")?;
    let mut username = String::new();
    file.read_to_string(&mut username)?;
    Ok(username)
}

// Using Result
match read_username_from_file() {
    Ok(username) => println!("Username: {}", username),
    Err(e) => println!("Error: {}", e),
}

Option Type

// Option for optional values
fn find_user(id: u32) -> Option<User> {
    if id == 1 {
        Some(User { id: 1, name: "Alice".to_string() })
    } else {
        None
    }
}

// Using Option
match find_user(1) {
    Some(user) => println!("Found: {}", user.name),
    None => println!("User not found"),
}

// Option combinators
let user = find_user(1)
    .map(|u| u.name)
    .unwrap_or("Unknown".to_string());

The ? Operator

// ? operator for error propagation
fn read_file(path: &str) -> Result<String, io::Error> {
    let mut file = File::open(path)?;  // Returns early if Err
    let mut contents = String::new();
    file.read_to_string(&mut contents)?;
    Ok(contents)
}

// Chaining with ?
fn process_file(path: &str) -> Result<usize, io::Error> {
    let contents = read_file(path)?;
    Ok(contents.len())
}

Custom Error Types

use std::fmt;

#[derive(Debug)]
enum AppError {
    Io(io::Error),
    Parse(std::num::ParseIntError),
    Custom(String),
}

impl fmt::Display for AppError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            AppError::Io(e) => write!(f, "IO error: {}", e),
            AppError::Parse(e) => write!(f, "Parse error: {}", e),
            AppError::Custom(s) => write!(f, "Error: {}", s),
        }
    }
}

impl From<io::Error> for AppError {
    fn from(error: io::Error) -> Self {
        AppError::Io(error)
    }
}

// Usage
fn process() -> Result<(), AppError> {
    let file = File::open("data.txt")?;  // Auto-converts io::Error
    // ...
    Ok(())
}

Traits

Defining Traits

// Define a trait
trait Summary {
    fn summarize(&self) -> String;

    // Default implementation
    fn summarize_author(&self) -> String {
        String::from("(Read more...)")
    }
}

// Implement trait
struct Article {
    title: String,
    content: String,
}

impl Summary for Article {
    fn summarize(&self) -> String {
        format!("{}: {}", self.title, self.content)
    }
}

Trait Bounds

// Function with trait bound
fn notify<T: Summary>(item: &T) {
    println!("Breaking news! {}", item.summarize());
}

// Multiple trait bounds
fn process<T: Summary + Display>(item: &T) {
    // ...
}

// Where clause (clearer for complex bounds)
fn complex<T, U>(t: &T, u: &U)
where
    T: Summary + Clone,
    U: Summary + Debug,
{
    // ...
}

// impl Trait syntax
fn returns_summarizable() -> impl Summary {
    Article {
        title: String::from("Title"),
        content: String::from("Content"),
    }
}

Common Traits

// Clone and Copy
#[derive(Clone)]
struct Point {
    x: i32,
    y: i32,
}

// Debug
#[derive(Debug)]
struct User {
    name: String,
    age: u32,
}

// PartialEq and Eq
#[derive(PartialEq, Eq)]
struct Id(u32);

// PartialOrd and Ord
#[derive(PartialOrd, Ord, PartialEq, Eq)]
struct Priority(u32);

Generics

Generic Functions

// Generic function
fn largest<T: PartialOrd>(list: &[T]) -> &T {
    let mut largest = &list[0];

    for item in list {
        if item > largest {
            largest = item;
        }
    }

    largest
}

// Usage
let numbers = vec![34, 50, 25, 100, 65];
let result = largest(&numbers);

let chars = vec!['y', 'm', 'a', 'q'];
let result = largest(&chars);

Generic Structs

// Generic struct
struct Point<T> {
    x: T,
    y: T,
}

impl<T> Point<T> {
    fn new(x: T, y: T) -> Self {
        Point { x, y }
    }
}

// Specific implementation for certain types
impl Point<f64> {
    fn distance_from_origin(&self) -> f64 {
        (self.x.powi(2) + self.y.powi(2)).sqrt()
    }
}

// Multiple type parameters
struct Pair<T, U> {
    first: T,
    second: U,
}

Generic Enums

// Option is a generic enum
enum Option<T> {
    Some(T),
    None,
}

// Result is a generic enum
enum Result<T, E> {
    Ok(T),
    Err(E),
}

Collections

Vectors

// Create vector
let mut v: Vec<i32> = Vec::new();
let v = vec![1, 2, 3];

// Add elements
v.push(4);
v.push(5);

// Access elements
let third = &v[2];  // Panics if out of bounds
let third = v.get(2);  // Returns Option<&T>

// Iterate
for i in &v {
    println!("{}", i);
}

// Iterate and modify
for i in &mut v {
    *i += 50;
}

HashMaps

use std::collections::HashMap;

// Create HashMap
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);

// Access values
let team = String::from("Blue");
let score = scores.get(&team);  // Returns Option<&V>

// Iterate
for (key, value) in &scores {
    println!("{}: {}", key, value);
}

// Update values
scores.entry(String::from("Blue")).or_insert(0);
*scores.entry(String::from("Blue")).or_insert(0) += 10;

Strings

// Create strings
let s = String::from("hello");
let s = "hello".to_string();

// Concatenation
let s1 = String::from("Hello, ");
let s2 = String::from("world!");
let s3 = s1 + &s2;  // s1 is moved

// format! macro
let s = format!("{}-{}", "hello", "world");

// Iterate
for c in "hello".chars() {
    println!("{}", c);
}

// Slicing (be careful with UTF-8!)
let hello = "Здравствуйте";
let s = &hello[0..4];  // "Зд"

Pattern Matching

Match Expressions

// Basic match
let number = 7;
match number {
    1 => println!("One"),
    2 | 3 | 5 | 7 | 11 => println!("Prime"),
    13..=19 => println!("Teen"),
    _ => println!("Other"),
}

// Match with destructuring
struct Point {
    x: i32,
    y: i32,
}

let p = Point { x: 0, y: 7 };
match p {
    Point { x: 0, y } => println!("On y axis at {}", y),
    Point { x, y: 0 } => println!("On x axis at {}", x),
    Point { x, y } => println!("At ({}, {})", x, y),
}

If Let

// if let for simple matches
let some_value = Some(3);

if let Some(3) = some_value {
    println!("three");
}

// With else
if let Some(x) = some_value {
    println!("{}", x);
} else {
    println!("None");
}

While Let

// while let for loops
let mut stack = vec![1, 2, 3];

while let Some(top) = stack.pop() {
    println!("{}", top);
}

Async Programming

Async Functions

use tokio;

// Async function
async fn fetch_data(url: &str) -> Result<String, reqwest::Error> {
    let response = reqwest::get(url).await?;
    let body = response.text().await?;
    Ok(body)
}

// Using async function
#[tokio::main]
async fn main() {
    match fetch_data("https://example.com").await {
        Ok(data) => println!("Data: {}", data),
        Err(e) => println!("Error: {}", e),
    }
}

Concurrent Async Operations

use tokio;

async fn fetch_multiple() {
    // Sequential
    let data1 = fetch_data("https://api1.com").await;
    let data2 = fetch_data("https://api2.com").await;

    // Concurrent with join!
    let (data1, data2) = tokio::join!(
        fetch_data("https://api1.com"),
        fetch_data("https://api2.com")
    );

    // Concurrent with spawn
    let handle1 = tokio::spawn(fetch_data("https://api1.com"));
    let handle2 = tokio::spawn(fetch_data("https://api2.com"));

    let data1 = handle1.await.unwrap();
    let data2 = handle2.await.unwrap();
}

Streams

use tokio_stream::StreamExt;

async fn process_stream() {
    let mut stream = tokio_stream::iter(vec![1, 2, 3, 4, 5]);

    while let Some(value) = stream.next().await {
        println!("Value: {}", value);
    }
}

Concurrency

Threads

use std::thread;
use std::time::Duration;

// Spawn thread
let handle = thread::spawn(|| {
    for i in 1..10 {
        println!("Thread: {}", i);
        thread::sleep(Duration::from_millis(1));
    }
});

handle.join().unwrap();

// Move data into thread
let v = vec![1, 2, 3];
let handle = thread::spawn(move || {
    println!("Vector: {:?}", v);
});

Channels

use std::sync::mpsc;

// Create channel
let (tx, rx) = mpsc::channel();

// Send from thread
thread::spawn(move || {
    tx.send("hello").unwrap();
});

// Receive
let received = rx.recv().unwrap();
println!("Received: {}", received);

// Multiple senders
let (tx, rx) = mpsc::channel();
let tx1 = tx.clone();

thread::spawn(move || tx.send("from thread 1").unwrap());
thread::spawn(move || tx1.send("from thread 2").unwrap());

for received in rx {
    println!("{}", received);
}

Shared State

use std::sync::{Arc, Mutex};

// Arc for shared ownership, Mutex for mutual exclusion
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());

Testing

Unit Tests

// Tests in same file
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_works() {
        assert_eq!(2 + 2, 4);
    }

    #[test]
    fn test_add() {
        assert_eq!(add(2, 2), 4);
    }

    #[test]
    #[should_panic]
    fn test_panic() {
        panic!("This should panic");
    }

    #[test]
    fn test_result() -> Result<(), String> {
        if 2 + 2 == 4 {
            Ok(())
        } else {
            Err(String::from("two plus two does not equal four"))
        }
    }
}

fn add(a: i32, b: i32) -> i32 {
    a + b
}

Integration Tests

// tests/integration_test.rs
use my_crate;

#[test]
fn test_integration() {
    assert_eq!(my_crate::add(2, 2), 4);
}

Running Tests

# Run all tests
cargo test

# Run specific test
cargo test test_add

# Run with output
cargo test -- --nocapture

# Run integration tests only
cargo test --test integration_test

Cargo and Dependencies

Cargo.toml

[package]
name = "my_project"
version = "0.1.0"
edition = "2021"

[dependencies]
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
reqwest = "0.11"

[dev-dependencies]
mockall = "0.11"

[profile.release]
opt-level = 3
lto = true

Common Commands

# Create new project
cargo new my_project
cargo new --lib my_lib

# Build
cargo build
cargo build --release

# Run
cargo run
cargo run --release

# Test
cargo test

# Check (faster than build)
cargo check

# Format code
cargo fmt

# Lint
cargo clippy

# Update dependencies
cargo update

# Add dependency
cargo add serde

Best Practices

Prefer Borrowing

// Good: Borrow when possible
fn process(data: &Vec<i32>) {
    // Use data without taking ownership
}

// Avoid: Taking ownership unless needed
fn process(data: Vec<i32>) {
    // Can't use data after calling this
}

Use ? for Error Propagation

// Good: Use ? operator
fn read_file(path: &str) -> Result<String, io::Error> {
    let mut file = File::open(path)?;
    let mut contents = String::new();
    file.read_to_string(&mut contents)?;
    Ok(contents)
}

// Avoid: Manual match for each error
fn read_file(path: &str) -> Result<String, io::Error> {
    let mut file = match File::open(path) {
        Ok(f) => f,
        Err(e) => return Err(e),
    };
    // ...
}

Use Iterators

// Good: Iterators (lazy, efficient)
let sum: i32 = vec![1, 2, 3, 4, 5]
    .iter()
    .filter(|x| *x % 2 == 0)
    .map(|x| x * 2)
    .sum();

// Avoid: Manual loops when iterators work
let mut sum = 0;
for x in vec![1, 2, 3, 4, 5] {
    if x % 2 == 0 {
        sum += x * 2;
    }
}

Prefer &str over &String

// Good: Accept string slices
fn greet(name: &str) {
    println!("Hello, {}", name);
}

// Can be called with both &str and &String
greet("Alice");
greet(&String::from("Bob"));

// Less flexible: Only accepts &String
fn greet(name: &String) {
    println!("Hello, {}", name);
}

Common Patterns

Builder Pattern

#[derive(Default)]
struct User {
    name: String,
    email: String,
    age: Option<u32>,
}

impl User {
    fn builder() -> UserBuilder {
        UserBuilder::default()
    }
}

#[derive(Default)]
struct UserBuilder {
    name: String,
    email: String,
    age: Option<u32>,
}

impl UserBuilder {
    fn name(mut self, name: impl Into<String>) -> Self {
        self.name = name.into();
        self
    }

    fn email(mut self, email: impl Into<String>) -> Self {
        self.email = email.into();
        self
    }

    fn age(mut self, age: u32) -> Self {
        self.age = Some(age);
        self
    }

    fn build(self) -> User {
        User {
            name: self.name,
            email: self.email,
            age: self.age,
        }
    }
}

// Usage
let user = User::builder()
    .name("Alice")
    .email("alice@example.com")
    .age(30)
    .build();

Newtype Pattern

// Newtype for type safety
struct Meters(f64);
struct Seconds(f64);

fn calculate_speed(distance: Meters, time: Seconds) -> f64 {
    distance.0 / time.0
}

// Can't accidentally swap parameters
let speed = calculate_speed(Meters(100.0), Seconds(9.8));

Key Principles

  • Ownership ensures memory safety: No garbage collector needed
  • Borrow checker prevents data races: Compile-time safety
  • Zero-cost abstractions: High-level code compiles to efficient machine code
  • Explicit over implicit: Be clear about ownership, mutability, errors
  • Prefer immutability: Use mut only when needed
  • Use the type system: Let the compiler catch errors
  • Test thoroughly: Tests are first-class in Rust
  • Use clippy: Catch common mistakes and non-idiomatic code

Sources

See sources.md for documentation references used to create this skill.