
RTK Rust Design Patterns
FreeImplement effective design patterns for Rust CLI tools.
Free · Opens the source repo
What RTK Rust Design Patterns does
The RTK Rust Design Patterns skill provides a collection of design patterns specifically tailored for Rust's filter module architecture, particularly in the context of command-line interface (CLI) tools. This skill is designed to guide developers through the implementation of various design patterns that enhance type safety, configuration management, resource management, and modularity in their Rust applications. The patterns included are Newtype, Builder, State Machine, Trait Object, RAII, Strategy, and Extension Trait, each accompanied by practical examples relevant to Rust development.
By leveraging these design patterns, developers can avoid common pitfalls such as type misuse and complex configuration management. For instance, the Newtype pattern helps ensure type safety by wrapping primitive types, preventing errors that can arise from mixing up values. The Builder pattern simplifies the creation of complex configurations, making the code more readable and maintainable. Additionally, the State Machine pattern is beneficial when dealing with parsing tasks that require context-sensitive behavior, while the Trait Object pattern facilitates a cleaner command dispatch mechanism.
This skill is particularly useful for developers working on CLI tools who are looking to implement robust and maintainable code structures. It is also beneficial for those refactoring existing modules, as it provides clear guidelines on how to incorporate these patterns effectively. Whether you are designing new modules or improving existing ones, the RTK Rust Design Patterns skill offers valuable insights and practical implementations that can enhance your development process.
When to use it
Use this skill when developing or refactoring Rust CLI tools, particularly when implementing complex configurations or ensuring type safety.
When not to use it
This skill may not be suitable for simple applications with straightforward requirements, where the overhead of design patterns could complicate the implementation unnecessarily.
What you can build with it
Implementing Type Safety
Use the Newtype pattern to wrap primitive types in your CLI tool, ensuring that different types cannot be mixed up, thus preventing silent bugs.
Creating Complex Configurations
Apply the Builder pattern when your configuration struct has multiple optional fields, allowing for clear and maintainable code without positional argument confusion.
Managing Resource Cleanup
Utilize the RAII pattern to manage resources like temporary files or database connections, ensuring that cleanup is handled automatically without explicit code.
How to install RTK Rust Design Patterns
View source1. Install with the skills CLI
npx skills add rtk-ai/rtk/design-patterns --agent claude-code2. Or install it manually
Download the skill folder and drop it into ~/.claude/skills/ for all projects, or .claude/skills/ to scope it to one repo. Restart Claude Code so it picks up the new skill.
Anthropic's agentic coding CLI, and the reference implementation of Agent Skills. Drop a skill folder into ~/.claude/skills and Claude Code loads it automatically whenever a task matches the skill's description. Claude Code docs
Inside SKILL.md
Written by rtk-aiRTK Rust Design Patterns
Patterns that apply to RTK's filter module architecture. Focused on CLI tool patterns, not web/service patterns.
Pattern 1: Newtype (Type Safety)
Use when: wrapping primitive types to prevent misuse (command names, paths, token counts).
// Without Newtype — easy to mix up
fn track(input_tokens: usize, output_tokens: usize) { ... }
track(output_tokens, input_tokens); // Silent bug!
// With Newtype — compile error on swap
pub struct InputTokens(pub usize);
pub struct OutputTokens(pub usize);
fn track(input: InputTokens, output: OutputTokens) { ... }
track(OutputTokens(100), InputTokens(400)); // Compile error ✅
// Practical RTK example: command name validation
pub struct CommandName(String);
impl CommandName {
pub fn new(s: &str) -> Result<Self> {
if s.contains(';') || s.contains('|') || s.contains('`') {
anyhow::bail!("Invalid command name: shell metacharacters");
}
Ok(Self(s.to_string()))
}
pub fn as_str(&self) -> &str { &self.0 }
}
Pattern 2: Builder (Complex Configuration)
Use when: a struct has 4+ optional fields, many with defaults.
#[derive(Default)]
pub struct FilterConfig {
max_lines: Option<usize>,
strip_ansi: bool,
show_warnings: bool,
truncate_at: Option<usize>,
}
impl FilterConfig {
pub fn new() -> Self { Self::default() }
pub fn max_lines(mut self, n: usize) -> Self { self.max_lines = Some(n); self }
pub fn strip_ansi(mut self, v: bool) -> Self { self.strip_ansi = v; self }
pub fn show_warnings(mut self, v: bool) -> Self { self.show_warnings = v; self }
}
// Usage — readable, no positional arg confusion
let config = FilterConfig::new()
.max_lines(50)
.strip_ansi(true)
.show_warnings(false);
When NOT to use Builder: if the struct has 1-3 fields with obvious meaning. Over-engineering for simple cases.
Pattern 3: State Machine (Parser/Filter Flows)
Use when: parsing multi-section output (test results, build output) where context changes behavior.
// RTK example: pytest output parsing
#[derive(Debug, PartialEq)]
enum ParseState {
LookingForTests,
InTestOutput,
InFailureSummary,
Done,
}
fn parse_pytest(input: &str) -> String {
let mut state = ParseState::LookingForTests;
let mut failures = Vec::new();
for line in input.lines() {
match state {
ParseState::LookingForTests => {
if line.contains("FAILED") || line.contains("ERROR") {
state = ParseState::InFailureSummary;
failures.push(line);
}
}
ParseState::InFailureSummary => {
if line.starts_with("=====") { state = ParseState::Done; }
else { failures.push(line); }
}
ParseState::Done => break,
_ => {}
}
}
failures.join("\n")
}
Pattern 4: Trait Object (Command Dispatch)
Use when: different command families need the same interface. Avoids massive match arms.
// Define a common interface for filters
pub trait OutputFilter {
fn filter(&self, input: &str) -> Result<String>;
fn command_name(&self) -> &str;
}
pub struct GitFilter;
pub struct CargoFilter;
impl OutputFilter for GitFilter {
fn filter(&self, input: &str) -> Result<String> { filter_git(input) }
fn command_name(&self) -> &str { "git" }
}
// RTK currently uses match-based dispatch in main.rs (simpler, no dynamic dispatch overhead)
// Trait objects are useful if filter registry becomes dynamic (e.g., TOML-loaded plugins)
Note: RTK's current match dispatch in main.rs is intentional — static dispatch, zero overhead. Only move to trait objects if the match arm count exceeds ~20 commands.
Pattern 5: RAII (Resource Management)
Use when: managing resources that need cleanup (temp files, SQLite connections).
// RTK tee.rs — RAII for temp output files
pub struct TeeFile {
path: PathBuf,
}
impl TeeFile {
pub fn create(content: &str) -> Result<Self> {
let path = tee_path()?;
fs::write(&path, content)
.with_context(|| format!("Failed to write tee file: {}", path.display()))?;
Ok(Self { path })
}
pub fn path(&self) -> &Path { &self.path }
}
// No explicit cleanup needed — file persists intentionally (rotation handled separately)
// If cleanup were needed: impl Drop { fn drop(&mut self) { let _ = fs::remove_file(&self.path); } }
Pattern 6: Strategy (Swappable Filter Logic)
Use when: a command has multiple filtering modes (e.g., compact vs. verbose).
pub enum FilterMode {
Compact, // Show only failures/errors
Summary, // Show counts + top errors
Full, // Pass through unchanged
}
pub fn apply_filter(input: &str, mode: FilterMode) -> String {
match mode {
FilterMode::Compact => filter_compact(input),
FilterMode::Summary => filter_summary(input),
FilterMode::Full => input.to_string(),
}
}
Pattern 7: Extension Trait (Add Methods to External Types)
Use when: you need to add methods to types you don't own (like &str for RTK-specific parsing).
pub trait RtkStrExt {
fn is_error_line(&self) -> bool;
fn is_warning_line(&self) -> bool;
fn token_count(&self) -> usize;
}
impl RtkStrExt for str {
fn is_error_line(&self) -> bool {
self.starts_with("error") || self.contains("[E")
}
fn is_warning_line(&self) -> bool {
self.starts_with("warning")
}
fn token_count(&self) -> usize {
self.split_whitespace().count()
}
}
// Usage
if line.is_error_line() { ... }
let tokens = output.token_count();
RTK Pattern Selection Guide
| Situation | Pattern | Avoid |
|---|---|---|
New *_cmd.rs filter module | Standard module pattern (see CLAUDE.md) | Over-abstracting |
| 4+ optional config fields | Builder | Struct literal |
| Multi-phase output parsing | State Machine | Nested if/else |
| Type-safe wrapper around string | Newtype | Raw String |
Adding methods to &str | Extension Trait | Free functions |
| Resource with cleanup | RAII / Drop | Manual cleanup |
| Dynamic filter registry | Trait Object | Match sprawl |
Anti-Patterns in RTK Context
// ❌ Generic over-engineering for one command
pub trait Filterable<T: CommandArgs + Send + Sync + 'static> { ... }
// ✅ Just write the function
pub fn filter_git_log(input: &str) -> Result<String> { ... }
// ❌ Singleton registry with global state
static FILTER_REGISTRY: Mutex<HashMap<String, Box<dyn Filter>>> = ...;
// ✅ Match in main.rs — simple, zero overhead, easy to trace
// ❌ Async traits for "future-proofing"
#[async_trait]
pub trait Filter { async fn apply(&self, input: &str) -> Result<String>; }
// ✅ Synchronous — RTK is single-threaded by design
pub trait Filter { fn apply(&self, input: &str) -> Result<String>; }
Frequently asked questions about RTK Rust Design Patterns
Similar skills
React Composition Patterns
Streamline your React component architecture with proven patterns.
Pester Should Migration
Easily convert Pester v5 assertions to v6 syntax.
Radix to Base UI Migration
Seamlessly migrate React components from Radix UI to Base UI.
Migrate Next.js to Vinext
Seamlessly transition your Next.js projects to Vinext.
WinUI 3 Migration Guide
Streamline your UWP to WinUI 3 migration process.
Refactor
Enhance code maintainability without altering behavior.
