From forge-dev
Rust development conventions — crate structure, error handling, CLI patterns, config loading, testing, and cross-platform compilation. USE WHEN writing Rust code, creating binaries, designing library APIs, or reviewing Rust implementations.
npx claudepluginhub n4m3z/forge-devThis skill uses the workspace's default tool permissions.
```
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Guides code writing, review, and refactoring with Karpathy-inspired rules to avoid overcomplication, ensure simplicity, surgical changes, and verifiable success criteria.
Provides UI/UX resources: 50+ styles, color palettes, font pairings, guidelines, charts for web/mobile across React, Next.js, Vue, Svelte, Tailwind, React Native, Flutter. Aids planning, building, reviewing interfaces.
Share bugs, ideas, or general feedback.
crate/
├── Cargo.toml
├── rustfmt.toml
├── src/
│ ├── lib.rs # Library crate root
│ ├── main.rs # Primary binary (coexists with lib.rs)
│ ├── error.rs # Error + ErrorKind
│ ├── config.rs # Config with serde + Default + load()
│ ├── cli/ # CLI subcommand handlers (binary-only)
│ ├── <domain>/ # Domain logic
│ │ ├── mod.rs
│ │ └── tests.rs # Sibling test file
│ └── bin/ # Additional tool binaries
└── tests/ # Integration tests
Library code stays pure — no stdout, no process::exit, no env access in core logic. Binaries handle I/O boundaries.
Use src/main.rs for the primary binary. Use src/bin/ only for secondary binaries. The primary binary declares its own modules (mod cli; resolves to src/cli/mod.rs).
Use a structured error type with kind + source chain:
pub struct Error {
kind: ErrorKind,
source: Option<Box<dyn std::error::Error + Send + Sync>>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ErrorKind {
Parse,
Config,
Transport,
}
impl Error {
pub fn new(kind: ErrorKind) -> Self {
Self { kind, source: None }
}
pub fn with_source(
kind: ErrorKind,
source: impl std::error::Error + Send + Sync + 'static,
) -> Self {
Self { kind, source: Some(Box::new(source)) }
}
pub fn kind(&self) -> ErrorKind { self.kind }
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self.kind)?;
if let Some(ref src) = self.source {
write!(f, ": {src}")?;
}
Ok(())
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.source.as_deref()
}
}
Callers branch on ErrorKind, not error text. Source errors are preserved for debugging. Make ErrorKind Copy for cheap comparison.
For internal retry/recovery logic, separate internal errors from public errors:
pub enum InternalError {
Transient(std::io::Error),
Fatal(Error),
}
impl InternalError {
pub fn is_retryable(&self) -> bool {
matches!(self, Self::Transient(_))
}
}
Binaries return ExitCode:
fn main() -> ExitCode {
match run() {
Ok(()) => ExitCode::SUCCESS,
Err(e) => {
eprintln!("Error: {e}");
ExitCode::from(1)
}
}
}
Replace .unwrap() with .unwrap_or_default(), ?, or explicit error handling.
Use clap derive with nested enums for subcommands:
use clap::{Parser, Subcommand};
#[derive(Parser)]
#[command(version)]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
Install {
#[command(subcommand)]
target: InstallTarget,
},
Validate {
root: Option<String>,
},
}
Each subcommand dispatches to a handler module returning ExitCode. Business logic stays in the library.
Bundle configuration as associated types in a context trait. Consumers choose concrete implementations; library code is generic:
pub trait Context {
type Transport: Send + Sync;
type Storage: Store;
type Key: Eq + Hash + Clone + Send + Sync;
}
pub struct Client<C: Context> {
transport: C::Transport,
storage: C::Storage,
}
Provide stub implementations for testing:
pub struct StubContext;
impl Context for StubContext {
type Transport = StubTransport;
type Storage = InMemoryStore;
type Key = String;
}
Compose request handling as trait layers:
pub trait SendRequest<Req, Res> {
type Err: std::error::Error + Send;
fn send(&self, req: Req) -> impl Future<Output = Result<Res, Self::Err>>;
}
Each middleware wraps a sender and adds behavior:
pub struct RetryHandler<S: SendRequest<Req, Res>> {
inner: S,
max_retries: usize,
}
impl<S: SendRequest<Req, Res>> SendRequest<Req, Res> for RetryHandler<S> {
async fn send(&self, req: Req) -> Result<Res, Self::Err> {
// retry logic wrapping self.inner.send(req)
}
}
Layers compose at compile time: App → Headers → Retry → Timeout → Transport.
Use type-state generics so the compiler prevents calling .build() before required configuration:
pub struct Builder<Transport = (), Storage = ()> {
transport: Transport,
storage: Storage,
timeout: Duration,
}
impl<S> Builder<(), S> {
pub fn with_transport(self, t: Http) -> Builder<Http, S> {
Builder { transport: t, storage: self.storage, timeout: self.timeout }
}
}
impl<T> Builder<T, ()> {
pub fn with_storage(self, s: FileStore) -> Builder<T, FileStore> {
Builder { transport: self.transport, storage: s, timeout: self.timeout }
}
}
impl Builder<Http, FileStore> {
pub fn build(self) -> Client { /* only callable when both are set */ }
}
No runtime validation, no Option<T> fields. Each builder method consumes self and returns a new type.
Control who can implement extension-point traits:
mod internal {
pub trait Sealed {}
}
pub trait ProvideInfo: internal::Sealed {
fn fingerprint(&self) -> String;
}
// Only types in this crate can implement Sealed, so only they can implement ProvideInfo.
// For testing, gate an unsealed blanket impl:
#[cfg(feature = "testing")]
impl<T> internal::Sealed for T {}
Configuration cascade: config.yaml (gitignored) overrides defaults.yaml (committed) overrides Default impl (compiled).
#[derive(Debug, Deserialize)]
#[serde(default)]
pub struct Config {
pub database: DatabaseConfig,
pub server: ServerConfig,
}
impl Config {
pub fn load(root: &Path) -> Self {
let path = if root.join("config.yaml").exists() {
root.join("config.yaml")
} else {
root.join("defaults.yaml")
};
std::fs::read_to_string(&path)
.ok()
.and_then(|c| serde_yaml::from_str(&c).ok())
.unwrap_or_default()
}
}
src/parse/
mod.rs # production code
tests.rs # #[cfg(test)] mod tests; imported from mod.rs
Rust has a built-in documentation system — rustdoc. Doc comments are markdown. cargo doc generates HTML, cargo test runs code examples.
Two comment styles:
/// on items (functions, structs, enums)//! at the top of a file (module-level documentation)Every public function follows this structure:
/// Split markdown content at `---` frontmatter delimiters.
///
/// Returns `(yaml_text, body)` if frontmatter is found.
/// Returns `None` if the content has no frontmatter.
///
/// # Examples
///
/// ```
/// use commands::parse;
///
/// let content = "---\nname: Test\n---\nBody text";
/// let (yaml, body) = parse::split_frontmatter(content).unwrap();
/// assert!(yaml.contains("name: Test"));
/// ```
///
/// # Errors
///
/// This function does not return errors — it returns `None` instead.
pub fn split_frontmatter(content: &str) -> Option<(&str, &str)>
Every module file starts with a //! block:
//! ## Parse
//!
//! Frontmatter extraction from markdown files. Splits content at `---`
//! delimiters and extracts YAML key-value pairs.
Standard sections in doc comments:
# Examples — runnable code block (tested by cargo test --doc)# Errors — when the function can fail# Panics — when the function can panic (should be never per RUST-0001)Use assert_cmd for binary-level tests:
#[test]
fn version_flag() {
Command::cargo_bin("mytool").unwrap()
.arg("--version")
.assert()
.success();
}
Use proptest for functions with large input spaces:
proptest! {
#[test]
fn config_load_never_panics(content in ".*") {
let _: Config = serde_yaml::from_str(&content).unwrap_or_default();
}
}
group_imports = "One"
imports_granularity = "Module"
merge_derives = false
wrap_comments = true
[lints.rust]
unsafe_code = "forbid"
[lints.clippy]
all = { level = "warn", priority = -1 }
pedantic = { level = "warn", priority = -1 }
module_name_repetitions = "allow"
must_use_candidate = "allow"
missing_errors_doc = "allow"
missing_panics_doc = "allow"
#[must_use]Annotate types and functions whose return values should never be silently dropped:
#[must_use]
pub struct Session { ... }
#[must_use]
pub fn validate(root: &Path) -> Suite { ... }
Organize hierarchically. Use dep: prefix for optional dependencies:
[features]
default = ["full"]
full = ["cli", "validate"]
cli = ["dep:clap"]
testing = ["dep:tempfile"]
fn pattern() -> &'static Regex {
static RE: OnceLock<Regex> = OnceLock::new();
RE.get_or_init(|| Regex::new(PATTERN).expect("invalid regex"))
}
cargo fmt + cargo clippy before every commit#[allow(unused)] — remove dead codeif let / let else over nested match arms.clone() — borrow where possiblebool start with is_ or has_std::path::Path and PathBuf — never string concatenation for paths#[cfg(target_os)] gates for platform-specific APIs@RustPatterns.md