Design Patterns

rtk-ai/rtk/.claude/skills/design-patterns

作者 rtk-aie4f05094d613无许可证82K 个星标收录于 2026年10月8日更新于 2026年10月8日仓库今天更新

Rust design patterns for RTK. Newtype, Builder, RAII, Trait Objects, State Machine. Applied to CLI filter modules. Use when designing new modules or refactoring existing ones.

AI 生成的概览

面向 RTK CLI 过滤模块的 Rust 设计模式参考指南,包含代码示例和模式选择表。

功能
介绍七种 Rust 设计模式——Newtype、Builder、状态机、Trait Object、RAII、Strategy 和扩展 Trait——并给出应用于 RTK CLI 过滤模块的 Rust 代码示例。还包含模式选择指南表和 RTK 语境下的反模式清单。产出的是指导建议和代码片段,而非生成文件。
适用场景
适用于设计新的 RTK 模块或重构现有模块、需要选择结构模式时。面向关于如何组织结构、某场景最适合哪种模式或按模式重构的问题。
运行要求
无脚本,仅为说明性内容。智能体需要文件读取与搜索工具(Read、Grep、Glob)来查看代码库。

RTK 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).

rust
// Without Newtype — easy to mix upfn track(input_tokens: usize, output_tokens: usize) { ... }track(output_tokens, input_tokens);  // Silent bug!
// With Newtype — compile error on swappub struct InputTokens(pub usize);pub struct OutputTokens(pub usize);fn track(input: InputTokens, output: OutputTokens) { ... }track(OutputTokens(100), InputTokens(400));  // Compile error ✅
rust
// Practical RTK example: command name validationpub 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.

rust
#[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 confusionlet 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.

rust
// 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.

rust
// Define a common interface for filterspub 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).

rust
// RTK tee.rs — RAII for temp output filespub 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).

rust
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).

rust
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()    }}
// Usageif line.is_error_line() { ... }let tokens = output.token_count();

RTK Pattern Selection Guide

SituationPatternAvoid
New *_cmd.rs filter moduleStandard module pattern (see CLAUDE.md)Over-abstracting
4+ optional config fieldsBuilderStruct literal
Multi-phase output parsingState MachineNested if/else
Type-safe wrapper around stringNewtypeRaw String
Adding methods to &strExtension TraitFree functions
Resource with cleanupRAII / DropManual cleanup
Dynamic filter registryTrait ObjectMatch sprawl

Anti-Patterns in RTK Context

rust
// ❌ Generic over-engineering for one commandpub trait Filterable<T: CommandArgs + Send + Sync + 'static> { ... }
// ✅ Just write the functionpub fn filter_git_log(input: &str) -> Result<String> { ... }
// ❌ Singleton registry with global statestatic 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 designpub trait Filter { fn apply(&self, input: &str) -> Result<String>; }

来源与署名

来源:rtk-ai/rtk位于.claude/skills/design-patterns提交e4f0509

许可证: 无许可证

内容归原作者所有。SourceWeft 从公开仓库中收录这些内容。

举报或申请下架