
Rust Expert
- 62 installs
- 36 repo stars
- Updated July 14, 2026
- oimiragieo/agent-studio
Helps with ai & agent building tasks.
About
rust-expert is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- rust-expert
- AI & Agent Building
- AI-coding skill
Rust Expert by the numbers
- 62 all-time installs (skills.sh)
- Ranked #6,310 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/oimiragieo/agent-studio --skill rust-expertAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 62 |
|---|---|
| repo stars | ★ 36 |
| Last updated | July 14, 2026 |
| Repository | oimiragieo/agent-studio ↗ |
What it does
Helps with ai & agent building tasks.
Files
Rust Expert
Apply idiomatic Rust patterns with strong safety, performance, and maintainability guarantees.
Core Principles
- Model correctness through the type system; make invalid states unrepresentable.
- Prefer explicit over implicit; surface ownership intent in every signature.
- Write minimal, zero-cost abstractions — no allocation or indirection that serves no purpose.
- Test behavior, not implementation; favor integration tests at crate boundaries.
---
1. Ownership, Borrowing, and Lifetimes
Ownership Patterns
// Prefer moves when value is consumed; prefer borrows when value is shared.
fn process(data: Vec<u8>) -> Vec<u8> { /* consumes */ }
fn inspect(data: &[u8]) -> usize { data.len() /* borrows */ }
// Clone only when necessary and explicitly documented.
// Anti-pattern: clone() to silence borrow checker errors — fix the design instead.Borrowing Rules (enforce at design time)
- Only one mutable reference OR any number of immutable references at a time.
- References must not outlive the value they point to.
- Use
Cow<'a, T>when you need borrow-or-own semantics without forcing allocation.
use std::borrow::Cow;
fn normalize(s: &str) -> Cow<str> {
if s.contains(' ') {
Cow::Owned(s.replace(' ', "_"))
} else {
Cow::Borrowed(s)
}
}Lifetime Patterns
// Explicit lifetime annotation: only when compiler cannot infer.
struct Parser<'input> {
source: &'input str,
pos: usize,
}
impl<'input> Parser<'input> {
fn next_token(&mut self) -> &'input str {
// Returns a slice of the original input — lifetime ties result to source.
&self.source[self.pos..]
}
}
// RPIT (Return Position Impl Trait) avoids lifetime noise in many cases.
fn words(s: &str) -> impl Iterator<Item = &str> {
s.split_whitespace()
}Anti-Patterns to Avoid
clone()inside hot loops to avoid borrow checker.unsafeto bypass lifetime checks — redesign instead.'staticbounds that force heap allocation when borrowing suffices.- Storing
&mut Tin structs — prefer owned data orRefCell<T>.
---
2. Error Handling
Decision Matrix
| Scenario | Tool |
|---|---|
| Library crate errors | thiserror — typed, composable |
| Application / binary errors | anyhow — ergonomic ? chaining |
| Domain-specific context | Custom enum via thiserror |
| Infallible conversions | From / Into — zero overhead |
thiserror (Library Crates)
use thiserror::Error;
#[derive(Debug, Error)]
pub enum ConfigError {
#[error("missing required field: {field}")]
MissingField { field: &'static str },
#[error("invalid value for {field}: {source}")]
ParseError { field: &'static str, #[source] source: std::num::ParseIntError },
#[error(transparent)]
Io(#[from] std::io::Error),
}anyhow (Application / Binary)
use anyhow::{Context, Result};
fn load_config(path: &str) -> Result<Config> {
let raw = std::fs::read_to_string(path)
.with_context(|| format!("reading config from {path}"))?;
toml::from_str(&raw).context("parsing config TOML")
}Error Propagation Rules
- Never use
.unwrap()in library code; use.expect()only in tests andmain()where the invariant is self-evident. - Add
.context()/.with_context()at every error boundary to preserve the call chain. - Map errors at crate boundaries — do not leak internal error types in public APIs.
---
3. Trait System
Generics vs Trait Objects
// Prefer generics (monomorphized, zero-cost) when types are known at compile time.
fn serialize<S: Serialize>(value: &S) -> Vec<u8> { /* ... */ }
// Use dyn Trait only when you need heterogeneous collections or runtime dispatch.
fn handlers() -> Vec<Box<dyn EventHandler>> { /* ... */ }Where Clauses
// Prefer where clauses for readability when bounds are complex.
fn merge<K, V>(a: HashMap<K, V>, b: HashMap<K, V>) -> HashMap<K, V>
where
K: Eq + Hash,
V: Clone,
{ /* ... */ }Blanket Implementations and Orphan Rules
- Implement standard traits (
Display,From,Iterator) for your types. - Respect the orphan rule: you may only implement a foreign trait for a local type.
- Use the newtype pattern to work around orphan restrictions.
struct Wrapper(Vec<u8>);
impl fmt::Display for Wrapper {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self.0)
}
}Rust 1.75+ Async in Traits (RPITIT)
// Stable as of Rust 1.75 — no more async-trait macro needed.
trait DataSource {
async fn fetch(&self, id: u64) -> anyhow::Result<Record>;
}
// Return-Position Impl Trait in Traits (RPITIT) — also stable in 1.75.
trait Transformer {
fn transform(&self, input: &str) -> impl Iterator<Item = String>;
}---
4. Async / Await with Tokio
Tokio Runtime Setup
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Default: multi-thread runtime (all CPU cores).
Ok(())
}
// Single-thread runtime for I/O-only workloads.
#[tokio::main(flavor = "current_thread")]
async fn main() -> anyhow::Result<()> { Ok(()) }Task Spawning
use tokio::task;
// Spawn a non-blocking async task.
let handle = task::spawn(async move {
fetch_data(url).await
});
let result = handle.await?; // propagate JoinError
// CPU-bound work must go to the blocking thread pool — never block the async runtime.
let result = task::spawn_blocking(|| {
expensive_cpu_computation()
}).await?;Channels
use tokio::sync::{mpsc, oneshot, broadcast, watch};
// mpsc: producer → consumer pipeline.
let (tx, mut rx) = mpsc::channel::<Bytes>(1024);
// oneshot: request / response pattern.
let (resp_tx, resp_rx) = oneshot::channel::<Result<Record>>();
// broadcast: fan-out to multiple subscribers.
let (tx, _rx) = broadcast::channel::<Event>(16);
// watch: latest-value semantics (config reload, health state).
let (tx, rx) = watch::channel(Config::default());select! and Cancellation
use tokio::select;
use tokio_util::sync::CancellationToken;
async fn worker(token: CancellationToken) {
select! {
_ = token.cancelled() => {
// Structured cancellation — always handle shutdown path.
}
result = do_work() => {
// Normal completion.
}
}
}Structured Concurrency
use tokio::task::JoinSet;
async fn fetch_all(urls: Vec<String>) -> Vec<anyhow::Result<Bytes>> {
let mut set = JoinSet::new();
for url in urls {
set.spawn(fetch(url));
}
let mut results = Vec::new();
while let Some(res) = set.join_next().await {
results.push(res.expect("task panicked"));
}
results
}Async Anti-Patterns
std::thread::sleepinside async context — usetokio::time::sleep.- Holding
MutexGuardacross.await— usetokio::sync::Mutexinstead. - Blocking I/O (file read, DNS) on async thread — use
spawn_blockingortokio::fs. - Unbounded channels — always set a capacity bound.
---
5. Common Crates
serde — Serialization
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct User {
pub id: u64,
pub display_name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub email: Option<String>,
}axum — HTTP Servers
use axum::{extract::{Path, State}, routing::get, Json, Router};
async fn get_user(
State(db): State<DbPool>,
Path(id): Path<u64>,
) -> Result<Json<User>, AppError> {
let user = db.find_user(id).await?;
Ok(Json(user))
}
let app = Router::new()
.route("/users/:id", get(get_user))
.with_state(db_pool);sqlx — Async Database
use sqlx::PgPool;
async fn insert_user(pool: &PgPool, name: &str) -> sqlx::Result<i64> {
let row = sqlx::query!("INSERT INTO users (name) VALUES ($1) RETURNING id", name)
.fetch_one(pool)
.await?;
Ok(row.id)
}reqwest — HTTP Client
use reqwest::Client;
async fn fetch_json<T: for<'de> serde::Deserialize<'de>>(
client: &Client,
url: &str,
) -> anyhow::Result<T> {
Ok(client.get(url).send().await?.error_for_status()?.json().await?)
}rayon — Data Parallelism
use rayon::prelude::*;
fn parallel_sum(data: &[f64]) -> f64 {
data.par_iter().sum()
}clap — CLI
use clap::Parser;
#[derive(Parser, Debug)]
#[command(version, about)]
struct Args {
#[arg(short, long, default_value = "8080")]
port: u16,
#[arg(short, long, env = "CONFIG_PATH")]
config: std::path::PathBuf,
}---
6. Performance Optimization
Zero-Cost Abstractions
- Prefer iterators over manual index loops — they compile to identical machine code.
- Use
#[inline]for hot, small functions that cross crate boundaries. - Prefer stack allocation; move to heap (
Box,Vec,Arc) only when necessary.
SIMD
// Use std::simd (nightly) or portable-simd crate for explicit vectorization.
// Profile first — LLVM auto-vectorizes most iterator chains.
use std::simd::{f32x8, SimdFloat};
fn dot_product_simd(a: &[f32], b: &[f32]) -> f32 {
a.chunks_exact(8)
.zip(b.chunks_exact(8))
.map(|(a_chunk, b_chunk)| {
let va = f32x8::from_slice(a_chunk);
let vb = f32x8::from_slice(b_chunk);
(va * vb).reduce_sum()
})
.sum()
}Profiling
# flamegraph (install: cargo install flamegraph)
cargo flamegraph --bin my-app
# perf stat for CPU counters (Linux)
perf stat cargo run --release
# heaptrack for heap allocation analysis (Linux)
heaptrack cargo run --release
# criterion for micro-benchmarks
cargo benchAllocation Awareness
// Preallocate when size is known.
let mut v = Vec::with_capacity(expected_len);
// String building: use write! into a pre-allocated String.
use std::fmt::Write;
let mut s = String::with_capacity(256);
write!(s, "id={}", id)?;
// Avoid format!() in hot paths — prefer direct write!() or push_str().---
7. Testing
Unit Tests
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_valid_config() {
let cfg = Config::from_str("[server]\nport = 9000").unwrap();
assert_eq!(cfg.port, 9000);
}
#[test]
fn parse_invalid_port_returns_error() {
let result = Config::from_str("[server]\nport = -1");
assert!(result.is_err());
}
}Integration Tests
// tests/integration/server.rs — tests the full public API.
#[tokio::test]
async fn health_endpoint_returns_200() {
let addr = spawn_test_server().await;
let resp = reqwest::get(format!("http://{addr}/health")).await.unwrap();
assert_eq!(resp.status(), 200);
}Property-Based Testing (proptest)
use proptest::prelude::*;
proptest! {
#[test]
fn encode_decode_roundtrip(data: Vec<u8>) {
let encoded = encode(&data);
prop_assert_eq!(decode(&encoded).unwrap(), data);
}
}Async Tests
#[tokio::test]
async fn fetch_returns_data() {
let mock = MockServer::start().await;
Mock::given(method("GET")).respond_with(ResponseTemplate::new(200)).mount(&mock).await;
let result = fetch(&mock.uri()).await;
assert!(result.is_ok());
}Test Conventions
- Test one behavior per test function.
- Use descriptive names:
<function>_<scenario>_<expected>. - Mock only external I/O boundaries (HTTP, filesystem, database).
- Run
cargo test -- --nocapturefor diagnostic output during development. - Run
cargo nextest runfor faster parallel test execution in CI.
---
8. Unsafe Rust
When to Use
- FFI boundaries (calling C functions, exporting to C).
- Low-level memory mapping or SIMD intrinsics when safe abstractions cannot reach.
- Performance-critical code where the safe alternative has measurable overhead (proved by profiling).
Guidelines
/// # Safety
///
/// - `ptr` must be non-null and properly aligned for `T`.
/// - The memory at `ptr` must be valid for `len` elements of type `T`.
/// - The caller must ensure no other mutable references to the memory exist.
pub unsafe fn from_raw_parts<T>(ptr: *const T, len: usize) -> &'static [T] {
std::slice::from_raw_parts(ptr, len)
}- Every
unsafeblock must have a// SAFETY:comment explaining why it is sound. - Minimize the scope of
unsafe— wrap in a safe abstraction immediately. - Prefer
unsafefn overunsafeblock inside a safe fn when the entire function is unsafe. - Use Miri (
cargo +nightly miri test) to detect undefined behavior in unsafe code.
---
9. FFI and Interop
Exporting to C
#[no_mangle]
pub extern "C" fn my_add(a: i32, b: i32) -> i32 {
a + b
}Calling C from Rust
extern "C" {
fn strlen(s: *const std::os::raw::c_char) -> usize;
}
fn rust_strlen(s: &str) -> usize {
let cstr = std::ffi::CString::new(s).expect("no null bytes");
// SAFETY: cstr is a valid, null-terminated C string.
unsafe { strlen(cstr.as_ptr()) }
}cbindgen / bindgen
- Use
cbindgento generate C headers from Rust public API. - Use
bindgento generate Rust bindings from C headers. - Always run through CI to detect API drift.
---
10. Build System
Cargo Workspaces
# Cargo.toml (workspace root)
[workspace]
members = ["crates/core", "crates/server", "crates/cli"]
resolver = "2"
[workspace.dependencies]
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
# crates/core/Cargo.toml
[dependencies]
tokio.workspace = true
serde.workspace = trueFeature Flags
[features]
default = ["std"]
std = []
async = ["dep:tokio"]
metrics = ["dep:prometheus"]
[dependencies]
tokio = { version = "1", optional = true }
prometheus = { version = "0.13", optional = true }#[cfg(feature = "metrics")]
fn record_latency(ms: u64) { /* ... */ }
#[cfg(not(feature = "metrics"))]
fn record_latency(_ms: u64) {}Build Scripts
// build.rs — runs before crate compilation.
fn main() {
// Rerun when proto files change.
println!("cargo:rerun-if-changed=proto/");
// Link a system library.
println!("cargo:rustc-link-lib=ssl");
}Useful Cargo Commands
cargo build --release # optimized build
cargo clippy -- -D warnings # lint (treat warnings as errors)
cargo fmt --check # format check (CI)
cargo doc --no-deps --open # generate and open docs
cargo audit # check dependencies for CVEs
cargo deny check # license + advisory checks
cargo expand # show macro expansion---
11. Rust 1.75+ Features
Async fn in Traits (Stable 1.75)
// No longer requires #[async_trait] crate.
trait Fetcher {
async fn fetch(&self, url: &str) -> anyhow::Result<Bytes>;
}RPITIT — Return-Position Impl Trait in Traits (Stable 1.75)
trait Source {
fn items(&self) -> impl Iterator<Item = &str>;
}let-else (Stable 1.65)
let Ok(value) = parse_value(raw) else {
return Err(ConfigError::InvalidValue);
};std::io::ErrorKind — richer variants (ongoing)
use std::io::ErrorKind;
match err.kind() {
ErrorKind::NotFound => { /* ... */ }
ErrorKind::PermissionDenied => { /* ... */ }
_ => { /* ... */ }
}---
12. Code Quality Checklist
Before marking Rust work complete:
- [ ]
cargo clippy -- -D warningspasses with zero warnings. - [ ]
cargo fmt --checkproduces no changes. - [ ]
cargo test(orcargo nextest run) passes — all tests green. - [ ] All
unsafeblocks have// SAFETY:comments. - [ ] Public API items have
///doc comments. - [ ] No
.unwrap()in library code except tests. - [ ] Error types derive
Debug+ implementstd::error::Error. - [ ] Async code does not block the executor thread.
- [ ] Performance-critical paths benchmarked before and after changes.
---
Assigned Agents
This skill is used by:
developer— Rust feature implementation and bug fixescode-reviewer— Rust-specific code review patternsqa— Rust testing strategies (proptest, criterion, nextest)
Memory Protocol (MANDATORY)
Before starting: Read .claude/context/memory/learnings.md
After completing:
- New Rust pattern discovered ->
.claude/context/memory/learnings.md - Rust-specific issue or gotcha ->
.claude/context/memory/issues.md - Architecture or crate selection decision ->
.claude/context/memory/decisions.md
ASSUME INTERRUPTION: Your context may reset. If it's not in memory, it didn't happen.
Invoke the rust-expert skill and follow it exactly as presented to you
'use strict';
function postExecute(_input = {}, result = {}) {
return result;
}
module.exports = { postExecute };
'use strict';
/**
* rust-expert pre-execute hook
*
* Validates that the skill input contains the required fields before
* the Rust expert workflow begins. Emits warnings for missing optional
* but recommended fields.
*/
function validateOneOf(input, key, validList, errors) {
const value = input[key];
if (value === undefined) return;
const list = validList.map(String);
const strVal = String(value);
if (!list.includes(strVal)) {
errors.push(`Invalid "${key}" value "${value}": must be one of ${list.join(', ')}`);
}
}
function preExecute(input = {}) {
const warnings = [];
const errors = [];
// ── Required field: task ────────────────────────────────────────────────────
if (!input.task || typeof input.task !== 'string' || input.task.trim() === '') {
errors.push(
'Missing required field "task": describe the Rust development task to perform ' +
'(e.g. "implement async HTTP client", "fix lifetime error in parser")'
);
}
// ── Recommended: context ────────────────────────────────────────────────────
if (!input.context || typeof input.context !== 'string' || input.context.trim() === '') {
warnings.push(
'Recommended field "context" is missing: provide background about the Rust codebase ' +
'(crate type, domain, existing dependencies, constraints)'
);
}
// ── Recommended: target / filePath ─────────────────────────────────────────
const hasTarget = input.target || input.filePath;
if (!hasTarget) {
warnings.push(
'Recommended field "filePath" (or "target") is missing: provide the path to the ' +
'Rust source file or project root so the skill can apply file-specific analysis'
);
}
validateOneOf(input, 'edition', ['2021', '2024'], errors);
validateOneOf(input, 'asyncRuntime', ['tokio', 'async-std', 'none'], errors);
validateOneOf(input, 'errorStrategy', ['thiserror', 'anyhow', 'custom', 'none'], errors);
// ── Warning: no error strategy specified ───────────────────────────────────
if (!input.errorStrategy) {
warnings.push(
'No "errorStrategy" specified. Rust best practice: ' +
'use "thiserror" for library crates, "anyhow" for application/binary crates. ' +
'Specify your approach so the skill gives targeted advice.'
);
}
// ── Emit ───────────────────────────────────────────────────────────────────
if (warnings.length > 0) {
for (const w of warnings) {
process.stderr.write(`[rust-expert pre-execute] WARNING: ${w}\n`);
}
}
if (errors.length > 0) {
for (const e of errors) {
process.stderr.write(`[rust-expert pre-execute] ERROR: ${e}\n`);
}
return {
continue: false,
error: `Validation failed with ${errors.length} error(s). See stderr for details.`,
errors,
warnings,
};
}
return { continue: true, warnings };
}
module.exports = { preExecute };
rust-expert Research Requirements
Generated: 2026-02-28
Skill Description
Rust programming expert including ownership, borrowing, lifetimes, async Tokio patterns, error handling, trait system, performance optimization, testing, and production systems development
Research Areas
- Current best practices for rust-expert
- Industry standards and tooling
- Integration patterns
Source References
- To be populated by skill-updater research phase
rust-expert Rules
Purpose
Rust programming expert including ownership, borrowing, lifetimes, async Tokio patterns, error handling, trait system, performance optimization, testing, and production systems development
Best Practices
- Follow established patterns
- Validate inputs at boundaries
Integration Points
See SKILL.md for complete documentation.
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "rust-expertInput",
"description": "Input schema for Rust programming expert including ownership, borrowing, lifetimes, async Tokio patterns, error handling, trait system, performance optimization, testing, and production systems development",
"type": "object",
"additionalProperties": true,
"properties": {
"target": {
"type": "string",
"description": "Target file or path for the skill to operate on"
},
"options": {
"type": "object",
"description": "Additional options for skill execution",
"additionalProperties": true
}
}
}
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "rust-expertOutput",
"type": "object",
"additionalProperties": true,
"properties": {
"ok": {
"type": "boolean"
},
"summary": {
"type": "string"
}
}
}
'use strict';
/**
* rust-expert skill CLI
* Domain-aware Rust project analyzer and validator.
*
* Usage:
* node main.cjs --help
* node main.cjs --validate <file_or_dir>
* node main.cjs --analyze <file_or_dir>
*/
const fs = require('fs');
const path = require('path');
// ─── Anti-pattern detection ───────────────────────────────────────────────────
const ANTI_PATTERNS = [
{
id: 'unwrap-in-lib',
pattern: /\.unwrap\(\)/g,
message: '.unwrap() found — use ? or .expect() with a clear message in lib code',
severity: 'error',
context: 'library',
},
{
id: 'clone-in-loop',
// Heuristic: .clone() inside a for/while block
pattern: /(?:for|while)[^{]*\{[^}]*\.clone\(\)/gs,
message: '.clone() inside a loop — consider borrowing or pre-cloning outside the loop',
severity: 'warning',
context: 'both',
},
{
id: 'blocking-in-async',
pattern: /(?:std::thread::sleep|std::fs::read|std::net::TcpStream::connect)/g,
message: 'Blocking call detected inside async context — use tokio equivalents',
severity: 'error',
context: 'async',
},
{
id: 'mutex-guard-across-await',
pattern: /std::sync::Mutex/g,
message: 'std::sync::Mutex may be held across .await — use tokio::sync::Mutex in async code',
severity: 'warning',
context: 'async',
},
{
id: 'unbounded-channel',
pattern: /(?:mpsc::channel\(\)|channel::<[^>]*>\(\))\s*(?!;)/g,
message: 'Unbounded channel — consider bounded mpsc::channel(capacity) to apply back-pressure',
severity: 'warning',
context: 'both',
},
{
id: 'non-idiomatic-error',
pattern: /Box<dyn\s+(?:std::)?error::Error>/g,
message:
'Box<dyn Error> in library code — prefer typed errors via thiserror for composable APIs',
severity: 'warning',
context: 'library',
},
];
// ─── Dependency & edition detection ──────────────────────────────────────────
function detectEdition(cargoToml) {
const match = cargoToml.match(/edition\s*=\s*"(\d{4})"/);
return match ? match[1] : 'unknown';
}
function detectAsyncRuntime(cargoToml) {
if (/tokio/.test(cargoToml)) return 'tokio';
if (/async-std/.test(cargoToml)) return 'async-std';
if (/smol/.test(cargoToml)) return 'smol';
return 'none';
}
function detectKeyDependencies(cargoToml) {
const deps = [];
const interestingCrates = [
'serde',
'tokio',
'async-std',
'axum',
'actix-web',
'warp',
'hyper',
'reqwest',
'sqlx',
'diesel',
'thiserror',
'anyhow',
'tracing',
'log',
'rayon',
'clap',
'structopt',
'proptest',
'criterion',
'nextest',
];
for (const crate of interestingCrates) {
const re = new RegExp(`\\b${crate}\\b`);
if (re.test(cargoToml)) deps.push(crate);
}
return deps;
}
// ─── Validate command ─────────────────────────────────────────────────────────
function validate(target) {
const results = { target, issues: [], warnings: [], ok: true };
const files = collectRustFiles(target);
if (files.length === 0) {
results.issues.push(`No .rs files found at: ${target}`);
results.ok = false;
return results;
}
for (const file of files) {
const src = fs.readFileSync(file, 'utf8');
const isLib = path.basename(file) === 'lib.rs' || file.includes(`${path.sep}lib${path.sep}`);
for (const ap of ANTI_PATTERNS) {
if (ap.context === 'library' && !isLib) continue;
if (ap.pattern.test(src)) {
const entry = { file, id: ap.id, message: ap.message };
if (ap.severity === 'error') {
results.issues.push(entry);
results.ok = false;
} else {
results.warnings.push(entry);
}
}
ap.pattern.lastIndex = 0; // reset stateful regex
}
}
return results;
}
// ─── Analyze command ──────────────────────────────────────────────────────────
function analyze(target) {
const result = { target, edition: 'unknown', asyncRuntime: 'none', dependencies: [] };
const cargoPath = findCargoToml(target);
if (cargoPath) {
const cargoToml = fs.readFileSync(cargoPath, 'utf8');
result.edition = detectEdition(cargoToml);
result.asyncRuntime = detectAsyncRuntime(cargoToml);
result.dependencies = detectKeyDependencies(cargoToml);
} else {
result.warning = 'No Cargo.toml found — edition and dependency analysis skipped';
}
return result;
}
// ─── File helpers ─────────────────────────────────────────────────────────────
function collectRustFiles(target) {
const stat = fs.statSync(target, { throwIfNoEntry: false });
if (!stat) return [];
if (stat.isFile()) return target.endsWith('.rs') ? [target] : [];
const files = [];
for (const entry of fs.readdirSync(target, { withFileTypes: true })) {
const full = path.join(target, entry.name);
if (entry.isDirectory() && entry.name !== 'target' && entry.name !== '.git') {
files.push(...collectRustFiles(full));
} else if (entry.isFile() && entry.name.endsWith('.rs')) {
files.push(full);
}
}
return files;
}
function findCargoToml(startDir) {
let dir = fs.statSync(startDir).isFile() ? path.dirname(startDir) : startDir;
for (let i = 0; i < 8; i++) {
const candidate = path.join(dir, 'Cargo.toml');
if (fs.existsSync(candidate)) return candidate;
const parent = path.dirname(dir);
if (parent === dir) break;
dir = parent;
}
return null;
}
// ─── CLI entry point ──────────────────────────────────────────────────────────
function printHelp() {
process.stdout.write(`rust-expert skill CLI
USAGE:
node main.cjs --help
node main.cjs --validate <path> Check a Rust file or project for anti-patterns
node main.cjs --analyze <path> Report edition, async runtime, key dependencies
OPTIONS:
--validate <path> Validate Rust source for common anti-patterns:
- .unwrap() in lib code
- .clone() in hot loops
- blocking calls in async context
- non-idiomatic error types
--analyze <path> Static analysis of project metadata:
- Rust edition (2021 / 2024)
- Async runtime (tokio / async-std / none)
- Key crates detected in Cargo.toml
--help Show this help text
EXAMPLES:
node main.cjs --validate src/lib.rs
node main.cjs --validate /path/to/my-crate
node main.cjs --analyze /path/to/my-crate
`);
}
function main(input = {}) {
const args = process.argv.slice(2);
if (args.includes('--help') || args.length === 0) {
printHelp();
return { ok: true, skill: 'rust-expert' };
}
const validateIdx = args.indexOf('--validate');
if (validateIdx !== -1) {
const target = args[validateIdx + 1];
if (!target) {
process.stderr.write('Error: --validate requires a path argument\n');
process.exit(1);
}
const result = validate(target);
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
if (!result.ok) process.exit(1);
return result;
}
const analyzeIdx = args.indexOf('--analyze');
if (analyzeIdx !== -1) {
const target = args[analyzeIdx + 1];
if (!target) {
process.stderr.write('Error: --analyze requires a path argument\n');
process.exit(1);
}
const result = analyze(target);
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
return result;
}
// Called programmatically with an input object
if (input.validate) return validate(input.validate);
if (input.analyze) return analyze(input.analyze);
printHelp();
return { ok: true, skill: 'rust-expert', input };
}
module.exports = { main, validate, analyze };
if (require.main === module) {
main();
}
rust-expert Implementation Template
Goal
- Define target outcome and acceptance criteria.
TDD
1. Red 2. Green 3. Refactor
Verification
- lint
- format
- targeted tests