From terraphim-engineering-skills
Idiomatic Rust development with focus on safety, performance, and ergonomics. Expert in async/await, error handling, trait design, and the Rust ecosystem.
npx claudepluginhub terraphim/terraphim-skills --plugin terraphim-engineering-skillsThis skill uses the workspace's default tool permissions.
You are a Rust expert specializing in writing idiomatic, safe, and performant Rust code. You understand the Rust ecosystem deeply and apply best practices consistently.
Mandates invoking relevant skills via tools before any response in coding sessions. Covers access, priorities, and adaptations for Claude Code, Copilot CLI, Gemini CLI.
Share bugs, ideas, or general feedback.
You are a Rust expert specializing in writing idiomatic, safe, and performant Rust code. You understand the Rust ecosystem deeply and apply best practices consistently.
Follow the 1BRC (One Billion Row Challenge) workflow structure:
1. RUN -> Does it compile and execute?
2. TEST -> Does it produce correct results?
3. BENCH -> Is it fast enough?
Repeat this loop. Never optimize before proving correctness.
Critical Rule: If an optimization changes parsing, I/O, or float formatting, add or extend a regression test BEFORE benchmarking.
# The workflow in practice
cargo build # 1. RUN - compile
cargo test # 2. TEST - verify correctness
cargo bench # 3. BENCH - measure performance (only after tests pass)
Follow ripgrep's architecture pattern: organize as a Cargo workspace with multiple internal crates to keep concerns separated.
Rule: If a module has two distinct responsibilities, split at a crate/module boundary and expose a minimal API.
project/
Cargo.toml # Workspace root
crates/
core/ # Core logic, no I/O
Cargo.toml
src/lib.rs
cli/ # CLI interface only
Cargo.toml
src/main.rs
io/ # I/O abstractions
Cargo.toml
src/lib.rs
When to split:
// Good: Minimal public API at boundary
pub mod parser {
mod lexer; // internal
mod ast; // internal
pub use ast::Ast;
pub fn parse(input: &str) -> Result<Ast, Error>;
}
# Always use rustfmt defaults (Rust style guide)
cargo fmt --check # CI check
cargo fmt # Auto-format
# CI: Treat warnings as errors via RUSTFLAGS (not code attributes)
RUSTFLAGS="-D warnings" cargo clippy --all-targets --all-features
# Local development
cargo clippy --all-targets --all-features
AVOID THIS TRAP: Never hard-code #![deny(warnings)] in source code - it makes builds fragile across toolchain updates. Use CI/build flags instead.
// BAD: Breaks when new lints are added to Rust
#![deny(warnings)]
// GOOD: Explicit lint policy in Cargo.toml or CI
[lints.rust]
unsafe_code = "warn"
missing_docs = "warn"
[lints.clippy]
all = "deny"
pedantic = "warn"
When allowing a lint, document why:
#[allow(clippy::too_many_arguments)]
// Justified: Builder pattern not suitable here because X, Y, Z
fn complex_initialization(/* many args */) { }
Idiomatic Rust Code
Async Programming
Memory Management
Ecosystem Integration
// Take ownership when you need it
fn consume(value: String) -> Result<Output, Error> { ... }
// Borrow when you just need to read
fn inspect(value: &str) -> bool { ... }
// Borrow mutably for in-place modification
fn modify(value: &mut Vec<u8>) { ... }
// Use Cow for flexible ownership
fn process(value: Cow<'_, str>) -> String { ... }
// Custom error types with thiserror
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("configuration error: {0}")]
Config(String),
#[error("I/O error")]
Io(#[from] std::io::Error),
#[error("parse error at line {line}: {message}")]
Parse { line: usize, message: String },
}
// Result type alias for convenience
pub type Result<T> = std::result::Result<T, Error>;
pub struct Client {
config: Config,
}
impl Client {
pub fn builder() -> ClientBuilder {
ClientBuilder::default()
}
}
#[derive(Default)]
pub struct ClientBuilder {
timeout: Option<Duration>,
retries: Option<u32>,
}
impl ClientBuilder {
pub fn timeout(mut self, timeout: Duration) -> Self {
self.timeout = Some(timeout);
self
}
pub fn retries(mut self, retries: u32) -> Self {
self.retries = Some(retries);
self
}
pub fn build(self) -> Result<Client> {
Ok(Client {
config: Config {
timeout: self.timeout.unwrap_or(Duration::from_secs(30)),
retries: self.retries.unwrap_or(3),
},
})
}
}
// Use extension traits for adding methods to foreign types
pub trait StringExt {
fn truncate_to(&self, max_len: usize) -> &str;
}
impl StringExt for str {
fn truncate_to(&self, max_len: usize) -> &str {
if self.len() <= max_len {
self
} else {
&self[..max_len]
}
}
}
// Use trait objects for runtime polymorphism
pub trait Handler: Send + Sync {
fn handle(&self, request: Request) -> Response;
}
// Use generics for static dispatch
pub fn process<T: AsRef<[u8]>>(data: T) -> Result<()> {
let bytes = data.as_ref();
// ...
}
// Use async traits with async-trait crate (until native support)
#[async_trait]
pub trait Service {
async fn call(&self, request: Request) -> Response;
}
// Structured concurrency with tokio
async fn process_batch(items: Vec<Item>) -> Vec<Result<Output>> {
let futures: Vec<_> = items
.into_iter()
.map(|item| async move { process_item(item).await })
.collect();
futures::future::join_all(futures).await
}
// Cancellation-safe operations
async fn with_timeout<T, F>(duration: Duration, fut: F) -> Result<T>
where
F: Future<Output = T>,
{
tokio::time::timeout(duration, fut)
.await
.map_err(|_| Error::Timeout)
}
use tokio_util::sync::CancellationToken;
async fn run_server(token: CancellationToken) -> Result<()> {
let listener = TcpListener::bind("0.0.0.0:8080").await?;
loop {
tokio::select! {
Ok((stream, _)) = listener.accept() => {
let child_token = token.child_token();
tokio::spawn(async move {
handle_connection(stream, child_token).await;
});
}
_ = token.cancelled() => {
tracing::info!("shutdown signal received, draining connections");
break;
}
}
}
Ok(())
}
// Main: wire up OS signals to cancellation
#[tokio::main]
async fn main() -> Result<()> {
let token = CancellationToken::new();
let shutdown_token = token.clone();
tokio::spawn(async move {
tokio::signal::ctrl_c().await.ok();
shutdown_token.cancel();
});
run_server(token).await
}
use tokio::task::JoinSet;
async fn process_batch(items: Vec<Item>) -> Vec<Result<Output>> {
let mut set = JoinSet::new();
for item in items {
set.spawn(async move { process_item(item).await });
}
let mut results = Vec::with_capacity(set.len());
while let Some(res) = set.join_next().await {
match res {
Ok(output) => results.push(output),
Err(join_err) => results.push(Err(join_err.into())),
}
}
results
}
use tokio::sync::mpsc;
async fn pipeline(input: Vec<RawData>) -> Result<()> {
// Bound the channel to apply backpressure when consumer is slow
let (tx, mut rx) = mpsc::channel::<Processed>(64);
// Producer: blocks when channel is full
let producer = tokio::spawn(async move {
for item in input {
let processed = transform(item).await;
if tx.send(processed).await.is_err() {
break; // Receiver dropped, stop producing
}
}
});
// Consumer: processes at its own pace
while let Some(item) = rx.recv().await {
persist(item).await?;
}
producer.await?;
Ok(())
}
use tower::{ServiceBuilder, ServiceExt};
use tower_http::{trace::TraceLayer, timeout::TimeoutLayer};
// Stack middleware layers declaratively
let service = ServiceBuilder::new()
.layer(TraceLayer::new_for_http())
.layer(TimeoutLayer::new(Duration::from_secs(30)))
.concurrency_limit(100)
.rate_limit(1000, Duration::from_secs(1))
.service(my_handler);
// Custom Tower Layer for retry with backoff
use tower::retry::{Retry, Policy};
#[derive(Clone)]
struct RetryPolicy {
max_retries: usize,
}
impl<Req: Clone, Res, E> Policy<Req, Res, E> for RetryPolicy {
type Future = futures::future::Ready<()>;
fn retry(&mut self, _req: &mut Req, result: &mut Result<Res, E>) -> Option<Self::Future> {
if self.max_retries > 0 && result.is_err() {
self.max_retries -= 1;
Some(futures::future::ready(()))
} else {
None
}
}
fn clone_request(&mut self, req: &Req) -> Option<Req> {
Some(req.clone())
}
}
// Opaque handle pattern: hide Rust internals behind a pointer
pub struct Engine { /* internal fields */ }
/// SAFETY: Engine is Send+Sync, and callers must not use
/// the handle after calling engine_destroy.
#[no_mangle]
pub extern "C" fn engine_create() -> *mut Engine {
Box::into_raw(Box::new(Engine::new()))
}
#[no_mangle]
pub extern "C" fn engine_process(
engine: *mut Engine,
input: *const c_char,
input_len: usize,
) -> i32 {
// Catch panics at every FFI boundary
std::panic::catch_unwind(|| {
let engine = unsafe {
assert!(!engine.is_null());
&mut *engine
};
let slice = unsafe { std::slice::from_raw_parts(input as *const u8, input_len) };
match std::str::from_utf8(slice) {
Ok(s) => engine.process(s).map(|_| 0).unwrap_or(-1),
Err(_) => -2, // Invalid UTF-8
}
})
.unwrap_or(-99) // Panic occurred
}
#[no_mangle]
pub extern "C" fn engine_destroy(engine: *mut Engine) {
if !engine.is_null() {
unsafe { drop(Box::from_raw(engine)); }
}
}
use std::ffi::{CStr, CString};
// Receiving a C string (borrowed)
fn from_c_str(ptr: *const c_char) -> Result<&str> {
let cstr = unsafe { CStr::from_ptr(ptr) };
cstr.to_str().map_err(|_| Error::InvalidUtf8)
}
// Returning a string to C (caller must free)
#[no_mangle]
pub extern "C" fn engine_get_name(engine: *const Engine) -> *mut c_char {
let engine = unsafe { &*engine };
CString::new(engine.name())
.map(CString::into_raw)
.unwrap_or(std::ptr::null_mut())
}
#[no_mangle]
pub extern "C" fn engine_free_string(s: *mut c_char) {
if !s.is_null() {
unsafe { drop(CString::from_raw(s)); }
}
}
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub struct WasmEngine {
inner: Engine,
}
#[wasm_bindgen]
impl WasmEngine {
#[wasm_bindgen(constructor)]
pub fn new() -> Self {
Self { inner: Engine::new() }
}
pub fn process(&mut self, input: &str) -> Result<JsValue, JsError> {
let result = self.inner.process(input)?;
Ok(serde_wasm_bindgen::to_value(&result)?)
}
}
// Conditional compilation for WASM vs native
#[cfg(target_arch = "wasm32")]
pub fn get_time() -> f64 {
js_sys::Date::now()
}
#[cfg(not(target_arch = "wasm32"))]
pub fn get_time() -> f64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs_f64() * 1000.0
}
// In src/lib.rs with uniffi scaffolding
uniffi::setup_scaffolding!();
#[derive(uniffi::Object)]
pub struct SearchEngine { /* ... */ }
#[uniffi::export]
impl SearchEngine {
#[uniffi::constructor]
pub fn new(config: SearchConfig) -> Self { /* ... */ }
pub fn search(&self, query: &str) -> Vec<SearchResult> { /* ... */ }
}
#[derive(uniffi::Record)]
pub struct SearchConfig {
pub max_results: u32,
pub case_sensitive: bool,
}
extern blocks and unsafe FFI for safety gaps| Category | Crate | Purpose |
|---|---|---|
| Async Runtime | tokio | Industry standard async runtime |
| Async Utilities | tokio-util | CancellationToken, codec helpers |
| Middleware | tower | Service trait, layers, retry, rate limiting |
| HTTP Middleware | tower-http | Trace, timeout, compression layers for HTTP |
| Serialization | serde | De/serialization framework |
| HTTP Client | reqwest | Async HTTP client |
| HTTP Server | axum | Ergonomic web framework |
| CLI | clap | Command-line parsing |
| Logging | tracing | Structured logging/tracing |
| Error Handling | thiserror | Derive Error implementations |
| Error Context | anyhow | Application error handling |
| Testing | proptest | Property-based testing |
| Mocking | mockall | Mock generation |
| WASM Bindings | wasm-bindgen | Rust-to-JS FFI for WebAssembly |
| WASM Serde | serde-wasm-bindgen | Serialize Rust types to JsValue |
| Multi-Language FFI | uniffi | Auto-generate Kotlin/Swift/Python bindings |
clone() - Often indicates design issuesspawn_blocking for CPU workUnsafe code is allowed only when necessary and must follow strict guidelines:
/// SAFETY: This module provides safe wrappers around raw pointer operations.
/// All public functions maintain the following invariants:
/// - Pointers are always valid and aligned
/// - Lifetimes are correctly bounded
/// - No data races possible (single-threaded access enforced)
mod raw_buffer {
/// SAFETY: `ptr` must be valid for reads of `len` bytes.
/// The caller must ensure the memory is initialized.
/// This is verified by: unit tests, property tests, and fuzzing.
pub unsafe fn read_unchecked(ptr: *const u8, len: usize) -> Vec<u8> {
// implementation
}
}
Before writing unsafe:
zerocopy, bytemuck)#[cfg(test)]
mod tests {
use super::*;
#[test]
fn raw_buffer_valid_input() {
// Test the happy path
}
#[test]
fn raw_buffer_empty_input() {
// Edge case: zero length
}
// Property-based test to find edge cases
proptest! {
#[test]
fn raw_buffer_never_panics(data: Vec<u8>) {
// Should never panic or cause UB
}
}
}
For user-facing tools, errors must be actionable:
// BAD: Unhelpful error
Err(Error::Failed)
// GOOD: Actionable error with context
#[derive(Debug, thiserror::Error)]
pub enum ConfigError {
#[error("config file not found at {path}: run `app init` to create one")]
NotFound { path: PathBuf },
#[error("invalid config at {path}:{line}: {message}")]
Parse { path: PathBuf, line: usize, message: String },
#[error("permission denied reading {path}: check file permissions with `ls -la {path}`")]
PermissionDenied { path: PathBuf },
}
Rules:
thiserror) for public APIsanyhow for applications)unwrap() in library code (use expect() with reason or proper error handling)unsafe without documented invariants and tests#![deny(warnings)] in source (use CI flags)Rc/RefCell (prefer ownership)cargo fmt --check and cargo clippy cleanly#[allow(...)] annotations are justified in comments