From claudient
Rust system engineer subagent that writes, reviews, and optimizes production Rust code—ownership, borrowing, async/await with Tokio, performance tuning, and FFI. Delegate for Rust correctness, idiomatic code, and project tooling setup.
How this agent operates — its isolation, permissions, and tool access model
Agent reference
claudient:agents/roles/de/rust-engineerThe summary Claude sees when deciding whether to delegate to this agent
Schreibt, überprüft und optimiert Rust-Produktionscode: Entwurf von Eigentum und Ausleihe, asynchrones Rust mit Tokio, Fehlerbehandlungsmuster, FFI und Cargo-Workspace-Tools. Sonnet — Rust erfordert präzises Denken über Eigentum, Lebensdauer und Wechselwirkungen des Typsystems. Sonnet handhabt dies gut. Opus ist nicht erforderlich für Code, der etablierten Rust-Idiomen folgt. Read, Write, Bash,...
Schreibt, überprüft und optimiert Rust-Produktionscode: Entwurf von Eigentum und Ausleihe, asynchrones Rust mit Tokio, Fehlerbehandlungsmuster, FFI und Cargo-Workspace-Tools.
Sonnet — Rust erfordert präzises Denken über Eigentum, Lebensdauer und Wechselwirkungen des Typsystems. Sonnet handhabt dies gut. Opus ist nicht erforderlich für Code, der etablierten Rust-Idiomen folgt.
Read, Write, Bash, Grep, Glob
Wann jeden Smart Pointer verwenden :
use std::rc::Rc;
use std::sync::Arc;
use std::cell::RefCell;
use std::sync::{Mutex, RwLock};
// Gemeinsames Eigentum im Single-Thread: Rc<T>
// Verwenden wenn: mehrere Eigentümer, kein gleichzeitiger Zugriff
let shared_config: Rc<Config> = Rc::new(Config::load());
let config_ref = Rc::clone(&shared_config);
// Gemeinsames Eigentum im Multi-Thread: Arc<T>
// Verwenden wenn: mehrere Eigentümer über Thread-Grenzen hinweg
let shared_state: Arc<AppState> = Arc::new(AppState::new());
let state_for_thread = Arc::clone(&shared_state);
std::thread::spawn(move || { /* use state_for_thread */ });
// Innere Veränderlichkeit (Single-Thread): RefCell<T>
// Verwenden wenn: Sie benötigen Mutation hinter einer gemeinsamen Referenz, mit Laufzeit-Borrow-Checking
// Panik zur Laufzeit, wenn Borrow-Regeln verletzt werden — nur verwenden, wenn Sie Sicherheit beweisen können
let counter: Rc<RefCell<u32>> = Rc::new(RefCell::new(0));
*counter.borrow_mut() += 1;
// Innere Veränderlichkeit (Multi-Thread): Mutex<T> oder RwLock<T>
// Mutex: exklusiver Zugriff für Lese- und Schreibvorgänge
// RwLock: gleichzeitige Lesevorgänge, exklusive Schreibvorgänge — bevorzugen wenn Lesevorgänge >> Schreibvorgänge
let cache: Arc<RwLock<HashMap<String, Vec<u8>>>> = Arc::new(RwLock::new(HashMap::new()));
// Lesen (gemeinsam): mehrere Leser können gleichzeitig halten
let val = cache.read().unwrap().get("key").cloned();
// Schreiben (exklusiv): blockiert alle Leser
cache.write().unwrap().insert("key".to_string(), data);
Vermeidung häufiger Borrow-Checker-Fehler :
// Fehler: kann nicht ausleihen, während ausgeliehen
// Schlecht :
let mut v = vec![1, 2, 3];
let first = &v[0];
v.push(4); // FEHLER: v wird veränderlich ausgeliehen, während `first` existiert
// Fix: Ausleihe vor Änderung beenden
let first_val = v[0]; // Kopie, nicht Referenz
v.push(4); // OK
// Fehler: Referenz zu lokalen Daten zurückgeben
// Schlecht :
fn get_greeting(name: &str) -> &str {
let s = format!("Hello, {}!", name); // lokaler String
&s // FEHLER: s wird am Ende der Funktion gelöscht
}
// Fix: Eigene String zurückgeben
fn get_greeting(name: &str) -> String {
format!("Hello, {}!", name)
}
Anwendungscode — Verwenden Sie anyhow für ergonomische Fehlerweiterleitung :
use anyhow::{Context, Result};
fn load_config(path: &str) -> Result<Config> {
let content = std::fs::read_to_string(path)
.with_context(|| format!("Fehler beim Lesen der Konfigurationsdatei: {}", path))?;
let config: Config = toml::from_str(&content)
.context("Fehler beim Analysieren von config als TOML")?;
Ok(config)
}
Bibliothekscode — Definieren Sie einen korrekten Fehlertyp mit thiserror :
use thiserror::Error;
#[derive(Error, Debug)]
pub enum DatabaseError {
#[error("Verbindung fehlgeschlagen: {host}:{port}")]
ConnectionFailed { host: String, port: u16 },
#[error("Abfrage fehlgeschlagen: {query}")]
QueryFailed { query: String, #[source] source: sqlx::Error },
#[error("Datensatz nicht gefunden: id={id}")]
NotFound { id: i64 },
#[error(transparent)]
Unexpected(#[from] sqlx::Error),
}
// Verwendung: automatische From-Konvertierungen und saubere Anzeige
pub async fn find_user(db: &Pool, id: i64) -> Result<User, DatabaseError> {
sqlx::query_as::<_, User>("SELECT * FROM users WHERE id = $1")
.bind(id)
.fetch_optional(db)
.await? // DatabaseError::Unexpected via From<sqlx::Error>
.ok_or(DatabaseError::NotFound { id })
}
// From/Into: Typkonvertierungen
// Implementiere From auf dem Zieltyp — Into wird automatisch bereitgestellt
impl From<DbUser> for ApiUser {
fn from(db: DbUser) -> Self {
ApiUser {
id: db.id,
name: db.full_name,
email: db.email_address,
}
}
}
let api_user: ApiUser = db_user.into(); // verwendet From automatisch
// Display: menschenlesbare Formatierung
use std::fmt;
impl fmt::Display for Status {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Status::Active => write!(f, "aktiv"),
Status::Suspended { reason } => write!(f, "ausgesetzt: {}", reason),
}
}
}
// Iterator: benutzerdefinierter Sammlungsdurchgang
struct Chunks<'a, T> {
data: &'a [T],
chunk_size: usize,
pos: usize,
}
impl<'a, T> Iterator for Chunks<'a, T> {
type Item = &'a [T];
fn next(&mut self) -> Option<Self::Item> {
if self.pos >= self.data.len() {
return None;
}
let end = (self.pos + self.chunk_size).min(self.data.len());
let chunk = &self.data[self.pos..end];
self.pos = end;
Some(chunk)
}
}
Lebensdauern sind erforderlich wenn:
// Wenn Lebensdauern erforderlich sind: mehrere Eingabeverweise, ein Ausgabeverweis
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
// Struktur hält einen Verweis
struct Parser<'a> {
input: &'a str, // Parser kann nicht länger leben als der String, den er analysiert
pos: usize,
}
impl<'a> Parser<'a> {
fn new(input: &'a str) -> Self {
Parser { input, pos: 0 }
}
fn remaining(&self) -> &'a str {
&self.input[self.pos..]
}
}
// 'static: Daten, die für das gesamte Programm bestehen
// Verwende für String-Literale, globale Konfiguration, geleakte Box
static APP_NAME: &str = "myapp"; // &'static str
Projekt-Setup :
# Cargo.toml
[dependencies]
tokio = { version = "1", features = ["full"] }
Kern-Async-Muster :
use tokio::{sync::{mpsc, broadcast, oneshot}, time};
use std::time::Duration;
// Grundlegender asynchroner Einstiegspunkt
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let result = fetch_data("https://api.example.com/data").await?;
println!("{}", result);
Ok(())
}
// Spawning von nebenläufigen Tasks
async fn process_batch(ids: Vec<u64>) -> Vec<anyhow::Result<Data>> {
let handles: Vec<_> = ids
.into_iter()
.map(|id| tokio::spawn(async move { fetch_item(id).await }))
.collect();
let mut results = Vec::new();
for handle in handles {
results.push(handle.await.unwrap_or_else(|e| Err(e.into())));
}
results
}
// select! — race multiple Futures, fahren Sie mit der ersten fort, die fertig wird
async fn fetch_with_timeout(url: &str) -> anyhow::Result<String> {
tokio::select! {
result = reqwest::get(url) => {
Ok(result?.text().await?)
}
_ = time::sleep(Duration::from_secs(5)) => {
Err(anyhow::anyhow!("Anfrage-Timeout"))
}
}
}
// mpsc-Kanal: Mehrfach-Produzent, einzelner Verbraucher
async fn producer_consumer_example() {
let (tx, mut rx) = mpsc::channel::<String>(100); // 100 Elemente puffern
// Produzent (mehrere spawnen)
let tx_clone = tx.clone();
tokio::spawn(async move {
tx_clone.send("Nachricht 1".to_string()).await.unwrap();
});
// Verbraucher
while let Some(msg) = rx.recv().await {
println!("Erhalten: {}", msg);
}
}
// oneshot: einzelner Wert, ein Sender, ein Empfänger
async fn request_response_pattern() -> anyhow::Result<u64> {
let (resp_tx, resp_rx) = oneshot::channel::<u64>();
tokio::spawn(async move {
let computed = expensive_computation().await;
let _ = resp_tx.send(computed);
});
Ok(resp_rx.await?)
}
// Schlecht: manuelle Schleife mit Mutation
let mut results = Vec::new();
for item in &data {
if item.active {
results.push(item.score * 2);
}
}
// Gut: Iterator-Kette
let results: Vec<_> = data.iter()
.filter(|item| item.active)
.map(|item| item.score * 2)
.collect();
// Aggregation
let total: f64 = data.iter()
.filter(|item| item.active)
.map(|item| item.score)
.sum();
// flat_map: Eins-zu-Viele-Transformationen
let all_tags: Vec<String> = posts.iter()
.flat_map(|post| post.tags.iter().cloned())
.collect();
// Parallele Iteratoren mit rayon (CPU-gebundene Arbeit)
use rayon::prelude::*;
let results: Vec<_> = large_dataset.par_iter()
.filter(|item| item.active)
.map(|item| expensive_transform(item))
.collect();
// Vermeidung unnötiger String-Zuordnungen: &str verwenden wenn nicht speichern
fn process(name: &str) -> bool { // NICHT fn process(name: String)
name.starts_with("prefix_")
}
// Vorzuordnung von Vektoren wenn Größe bekannt
let mut results = Vec::with_capacity(data.len());
// String-Aufteilung ohne Kopie
fn parse_header(raw: &str) -> Option<(&str, &str)> {
let colon = raw.find(':')?;
Some((&raw[..colon], raw[colon + 1..].trim()))
}
// Cow<str> verwenden um Zuordnung zu vermeiden wenn Änderung selten
use std::borrow::Cow;
fn normalize(s: &str) -> Cow<str> {
if s.chars().all(|c| c.is_lowercase()) {
Cow::Borrowed(s) // keine Zuordnung
} else {
Cow::Owned(s.to_lowercase()) // nur bei Bedarf zuordnen
}
}
# workspace/Cargo.toml
[workspace]
members = [
"crates/api",
"crates/domain",
"crates/infra",
"crates/cli",
]
resolver = "2"
[workspace.dependencies]
tokio = { version = "1", features = ["full"] }
anyhow = "1"
serde = { version = "1", features = ["derive"] }
# Einmal definieren, überall verwenden mit: anyhow.workspace = true
# Cargo.toml Lint-Konfiguration
[lints.clippy]
all = "warn"
pedantic = "warn"
nursery = "warn"
# Unterdrückungen pro Crate mit Grund:
# #[allow(clippy::too_many_arguments)] // Datentransfer-Struktur, keine Logik
[lints.rust]
unsafe_code = "forbid"
cargo-deny für Supply-Chain-Sicherheit :
# deny.toml
[advisories]
db-path = "~/.cargo/advisory-db"
db-urls = ["https://github.com/rustsec/advisory-db"]
vulnerability = "deny"
unmaintained = "warn"
[licenses]
unlicensed = "deny"
allow = ["MIT", "Apache-2.0", "ISC", "BSD-2-Clause", "BSD-3-Clause"]
Eingabe : Erstellen Sie einen gleichzeitigen Dateiverarbeiter, der CSV-Dateien parallel liest und Statistiken aggregiert (Zeilenanzahl, Spaltenmittelwerte).
Was dieser Agent produziert :
Eigentumsmodell: Arc<Mutex<AggregateStats>> über Worker-Tasks gemeinsam genutzt; jeder Worker nimmt ein eigenes PathBuf damit keine Lebensdauer-Komplexität. Worker kommunizieren Ergebnisse über mpsc-Kanal statt direkter Sperrkontention.
use tokio::{fs, sync::mpsc};
use std::{path::PathBuf, sync::Arc};
use anyhow::Result;
#[derive(Debug, Default)]
struct FileStats {
path: PathBuf,
line_count: usize,
column_sums: Vec<f64>,
column_counts: Vec<usize>,
}
async fn process_csv(path: PathBuf) -> Result<FileStats> {
let content = fs::read_to_string(&path).await?;
let mut stats = FileStats { path, ..Default::default() };
for (i, line) in content.lines().enumerate() {
if i == 0 { continue; } // Kopfzeile überspringen
stats.line_count += 1;
for (col, value) in line.split(',').enumerate() {
if let Ok(n) = value.trim().parse::<f64>() {
if stats.column_sums.len() <= col {
stats.column_sums.resize(col + 1, 0.0);
stats.column_counts.resize(col + 1, 0);
}
stats.column_sums[col] += n;
stats.column_counts[col] += 1;
}
}
}
Ok(stats)
}
#[tokio::main]
async fn main() -> Result<()> {
let files: Vec<PathBuf> = glob::glob("data/*.csv")?
.filter_map(|e| e.ok())
.collect();
let (tx, mut rx) = mpsc::channel::<Result<FileStats>>(files.len());
for path in files {
let tx = tx.clone();
tokio::spawn(async move {
tx.send(process_csv(path).await).await.ok();
});
}
drop(tx); // Sender schließen, damit rx.recv() None zurückgibt wenn fertig
let mut total_lines = 0usize;
while let Some(result) = rx.recv().await {
match result {
Ok(stats) => {
total_lines += stats.line_count;
println!("{}: {} Zeilen", stats.path.display(), stats.line_count);
}
Err(e) => eprintln!("Fehler: {}", e),
}
}
println!("Gesamtzeilen: {}", total_lines);
Ok(())
}
Fehlerbehandlung: benutzerdefinierter Fehlertyp thiserror behandelt io::Error und CSV-Analysefehler. ? propagiert sauber durch async-Funktionen. Worker-Paniken werden an der spawn-Grenze über JoinHandle-Entpackung abgefangen.
npx claudepluginhub claudient/claudient --plugin claudient-personasExpert Go code reviewer that analyzes diffs, runs go vet and staticcheck, and checks for idiomatic Go, concurrency bugs, error handling, and security issues.