Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
722 changes: 722 additions & 0 deletions .agents/skills/react/SKILL.md

Large diffs are not rendered by default.

335 changes: 335 additions & 0 deletions .agents/skills/rust-skills/AGENTS.md

Large diffs are not rendered by default.

335 changes: 335 additions & 0 deletions .agents/skills/rust-skills/CLAUDE.md

Large diffs are not rendered by default.

21 changes: 21 additions & 0 deletions .agents/skills/rust-skills/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2025 Leonardo Maldonado

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
335 changes: 335 additions & 0 deletions .agents/skills/rust-skills/SKILL.md

Large diffs are not rendered by default.

124 changes: 124 additions & 0 deletions .agents/skills/rust-skills/rules/anti-clone-excessive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# anti-clone-excessive

> Don't clone when borrowing works

## Why It Matters

`.clone()` allocates memory and copies data. When you only need to read data, borrowing (`&T`) is free. Excessive cloning wastes memory, CPU cycles, and often indicates misunderstanding of ownership.

## Bad

```rust
// Cloning to pass to a function that only reads
fn print_name(name: String) { // Takes ownership
println!("{}", name);
}
let name = "Alice".to_string();
print_name(name.clone()); // Unnecessary clone
print_name(name); // Could have just done this

// Cloning in a loop
for item in items.clone() { // Clones entire Vec
process(&item);
}

// Cloning for comparison
if input.clone() == expected { // Pointless clone
// ...
}

// Cloning struct fields
fn get_name(&self) -> String {
self.name.clone() // Caller might not need ownership
}
```

## Good

```rust
// Accept reference if only reading
fn print_name(name: &str) {
println!("{}", name);
}
let name = "Alice".to_string();
print_name(&name); // Borrow, no clone

// Iterate by reference
for item in &items {
process(item);
}

// Compare by reference
if input == expected {
// ...
}

// Return reference when possible
fn get_name(&self) -> &str {
&self.name
}
```

## When to Clone

```rust
// Need owned data for async move
let name = name.clone();
tokio::spawn(async move {
process(name).await;
});

// Storing in a new struct
struct Cache {
data: String,
}
impl Cache {
fn store(&mut self, data: &str) {
self.data = data.to_string(); // Must own
}
}

// Multiple owners (use Arc instead if frequent)
let shared = data.clone();
thread::spawn(move || use_data(shared));
```

## Alternatives to Clone

| Instead of | Use |
|------------|-----|
| `s.clone()` for reading | `&s` |
| `vec.clone()` for iteration | `&vec` or `vec.iter()` |
| `Clone` for shared ownership | `Arc<T>` |
| Clone in hot loop | Move outside loop |
| `s.to_string()` from `&str` | Accept `&str` if possible |

## Pattern: Clone on Write

```rust
use std::borrow::Cow;

fn process(input: Cow<str>) -> Cow<str> {
if needs_modification(&input) {
Cow::Owned(modify(&input)) // Clone only if needed
} else {
input // No clone
}
}
```

## Detecting Excessive Clones

```toml
# Cargo.toml
[lints.clippy]
clone_on_copy = "warn"
clone_on_ref_ptr = "warn"
redundant_clone = "warn"
```

## See Also

- [own-borrow-over-clone](./own-borrow-over-clone.md) - Borrowing patterns
- [own-cow-conditional](./own-cow-conditional.md) - Clone on write
- [own-arc-shared](./own-arc-shared.md) - Shared ownership
131 changes: 131 additions & 0 deletions .agents/skills/rust-skills/rules/anti-collect-intermediate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# anti-collect-intermediate

> Don't collect intermediate iterators

## Why It Matters

Each `.collect()` allocates a new collection. Collecting intermediate results in a chain creates unnecessary allocations and prevents iterator fusion. Keep the chain lazy; collect only at the end.

## Bad

```rust
// Three allocations, three passes
fn process(data: Vec<i32>) -> Vec<i32> {
let step1: Vec<_> = data.into_iter()
.filter(|x| *x > 0)
.collect();

let step2: Vec<_> = step1.into_iter()
.map(|x| x * 2)
.collect();

step2.into_iter()
.filter(|x| *x < 100)
.collect()
}

// Collecting just to check length
fn has_valid_items(items: &[Item]) -> bool {
let valid: Vec<_> = items.iter()
.filter(|i| i.is_valid())
.collect();
!valid.is_empty()
}

// Collecting to iterate again
fn sum_valid(items: &[Item]) -> i64 {
let valid: Vec<_> = items.iter()
.filter(|i| i.is_valid())
.collect();
valid.iter().map(|i| i.value).sum()
}
```

## Good

```rust
// Single allocation, single pass
fn process(data: Vec<i32>) -> Vec<i32> {
data.into_iter()
.filter(|x| *x > 0)
.map(|x| x * 2)
.filter(|x| *x < 100)
.collect()
}

// No allocation - iterator short-circuits
fn has_valid_items(items: &[Item]) -> bool {
items.iter().any(|i| i.is_valid())
}

// No intermediate allocation
fn sum_valid(items: &[Item]) -> i64 {
items.iter()
.filter(|i| i.is_valid())
.map(|i| i.value)
.sum()
}
```

## When Collection Is Needed

```rust
// Need to iterate twice
let valid: Vec<_> = items.iter()
.filter(|i| i.is_valid())
.collect();
let count = valid.len();
for item in &valid {
process(item);
}

// Need to sort (requires concrete collection)
let mut sorted: Vec<_> = items.iter()
.filter(|i| i.is_active())
.collect();
sorted.sort_by_key(|i| i.priority);

// Need random access
let indexed: Vec<_> = items.iter().collect();
let middle = indexed.get(indexed.len() / 2);
```

## Iterator Methods That Avoid Collection

| Instead of Collecting to... | Use |
|-----------------------------|-----|
| Check if empty | `.any(|_| true)` or `.next().is_some()` |
| Check if any match | `.any(predicate)` |
| Check if all match | `.all(predicate)` |
| Count elements | `.count()` |
| Sum elements | `.sum()` |
| Find first | `.find(predicate)` |
| Get first | `.next()` |
| Get last | `.last()` |

## Pattern: Deferred Collection

```rust
// Return iterator, let caller collect if needed
fn valid_items(items: &[Item]) -> impl Iterator<Item = &Item> {
items.iter().filter(|i| i.is_valid())
}

// Caller decides
let count = valid_items(&items).count(); // No collection
let vec: Vec<_> = valid_items(&items).collect(); // Collection when needed
```

## Comparison

| Pattern | Allocations | Passes |
|---------|-------------|--------|
| `.collect()` each step | N | N |
| Single chain, one `.collect()` | 1 | 1 |
| No collection (streaming) | 0 | 1 |

## See Also

- [perf-collect-once](./perf-collect-once.md) - Single collect
- [perf-iter-lazy](./perf-iter-lazy.md) - Lazy evaluation
- [perf-iter-over-index](./perf-iter-over-index.md) - Iterator patterns
Loading