Communitygithub.com

rust-패턴

안전하고 성능 좋은 애플리케이션 구축을 위한 관용적 Rust 패턴: 소유권, 에러 처리, 트레이트, 동시성 및 모범 사례. Rust 코드를 작성하거나 리뷰할 때, 소유권·에러 처리·트레이트·동시성 관련 문제가 있을 때 사용하세요.

rust-패턴란 무엇인가요?

이 스킬은 Rust 개발자가 안전하고 성능 좋으며 유지보수하기 쉬운 애플리케이션을 작성하도록 돕는 포괄적인 패턴과 모범 사례 모음입니다. 소유권과 빌림(Cow 사용 포함), Result/`?` 연산자를 통한 에러 처리(thiserror/anyhow 사용), 열거형과 철저한 패턴 매칭으로 불가능한 상태를 표현 불가능하게 만드는 방법, 트레이트와 제네릭을 통한 제로-코스트 추상화, Arc<Mutex<T>>/채널/async-await를 활용한 안전한 동시성, 최소한의 pub 노출 등 여섯 가지 핵심 영역을 다룹니다. 이 스킬은 Rust 코드를 작성하거나 리뷰할 때 지침을 제공하며, Cargo 및 Clippy, rustfmt 등 Rust 생태계 도구와 함께 사용하도록 설계되었습니다. 모든 예제는 실제 Rust 코드로 제공되며, 안티패턴과 그 대안도 함께 제시하여 Rust 언어의 타입 시스템과 소유권 모델을 최대한 활용할 수 있도록 안내합니다.

지원 대상~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/ECC/tree/main/skills/rust-patterns

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

Rust 개발 패턴

안전하고 성능 좋으며 유지보수하기 쉬운 애플리케이션을 구축하기 위한 관용적 Rust 패턴과 모범 사례입니다.

사용 시기

  • 새로운 Rust 코드를 작성할 때
  • Rust 코드를 리뷰할 때
  • 기존 Rust 코드를 리팩터링할 때
  • 크레이트 구조와 모듈 레이아웃을 설계할 때

동작 방식

이 스킬은 다음 여섯 가지 주요 영역에서 관용적 Rust 규칙을 강화합니다: 컴파일 타임에 데이터 경합을 방지하는 소유권과 빌림, thiserror(라이브러리)와 anyhow(애플리케이션)를 사용한 Result/? 에러 전파, 불가능한 상태를 표현 불가능하게 만드는 열거형과 철저한 패턴 매칭, 제로-코스트 추상화를 위한 트레이트와 제네릭, Arc<Mutex<T>>, 채널, async/await를 통한 안전한 동시성, 그리고 도메인별로 구성된 최소한의 pub 노출 영역입니다.

핵심 원칙

1. 소유권과 빌림

Rust의 소유권 시스템은 데이터 경합과 메모리 버그를 컴파일 타임에 방지합니다.

// 좋음: 소유권이 필요하지 않을 때는 참조를 전달하세요
fn process(data: &[u8]) -> usize {
    data.len()
}

// 좋음: 저장하거나 소비해야 할 때만 소유권을 가져오세요
fn store(data: Vec<u8>) -> Record {
    Record { payload: data }
}

// 나쁨: 빌림 검사기를 피하려고 불필요하게 복제하기
fn process_bad(data: &Vec<u8>) -> usize {
    let cloned = data.clone(); // 낭비입니다 — 그냥 빌리세요
    cloned.len()
}

유연한 소유권을 위한 Cow 사용

use std::borrow::Cow;

fn normalize(input: &str) -> Cow<'_, str> {
    if input.contains(' ') {
        Cow::Owned(input.replace(' ', "_"))
    } else {
        Cow::Borrowed(input) // 변형이 필요 없을 때는 제로-코스트
    }
}

에러 처리

Result와 ? 사용 — 프로덕션에서는 절대 unwrap() 금지

// 좋음: 컨텍스트와 함께 에러 전파
use anyhow::{Context, Result};

fn load_config(path: &str) -> Result<Config> {
    let content = std::fs::read_to_string(path)
        .with_context(|| format!("failed to read config from {path}"))?;
    let config: Config = toml::from_str(&content)
        .with_context(|| format!("failed to parse config from {path}"))?;
    Ok(config)
}

// 나쁨: 에러 시 패닉
fn load_config_bad(path: &str) -> Config {
    let content = std::fs::read_to_string(path).unwrap(); // 패닉!
    toml::from_str(&content).unwrap()
}

라이브러리 에러는 thiserror, 애플리케이션 에러는 anyhow

// 라이브러리 코드: 구조화된 타입 에러
use thiserror::Error;

#[derive(Debug, Error)]
pub enum StorageError {
    #[error("record not found: {id}")]
    NotFound { id: String },
    #[error("connection failed")]
    Connection(#[from] std::io::Error),
    #[error("invalid data: {0}")]
    InvalidData(String),
}

// 애플리케이션 코드: 유연한 에러 처리
use anyhow::{bail, Result};

fn run() -> Result<()> {
    let config = load_config("app.toml")?;
    if config.workers == 0 {
        bail!("worker count must be > 0");
    }
    Ok(())
}

중첩 매칭 대신 Option 컴비네이터

// 좋음: 컴비네이터 체인
fn find_user_email(users: &[User], id: u64) -> Option<String> {
    users.iter()
        .find(|u| u.id == id)
        .map(|u| u.email.clone())
}

// 나쁨: 깊게 중첩된 매칭
fn find_user_email_bad(users: &[User], id: u64) -> Option<String> {
    match users.iter().find(|u| u.id == id) {
        Some(user) => match &user.email {
            email => Some(email.clone()),
        },
        None => None,
    }
}

열거형과 패턴 매칭

상태를 열거형으로 모델링

// 좋음: 불가능한 상태는 표현 불가능
enum ConnectionState {
    Disconnected,
    Connecting { attempt: u32 },
    Connected { session_id: String },
    Failed { reason: String, retries: u32 },
}

fn handle(state: &ConnectionState) {
    match state {
        ConnectionState::Disconnected => connect(),
        ConnectionState::Connecting { attempt } if *attempt > 3 => abort(),
        ConnectionState::Connecting { .. } => wait(),
        ConnectionState::Connected { session_id } => use_session(session_id),
        ConnectionState::Failed { retries, .. } if *retries < 5 => retry(),
        ConnectionState::Failed { reason, .. } => log_failure(reason),
    }
}

철저한 매칭 — 비즈니스 로직에는 Catch-All 금지

// 좋음: 모든 변형을 명시적으로 처리
match command {
    Command::Start => start_service(),
    Command::Stop => stop_service(),
    Command::Restart => restart_service(),
    // 새 변형을 추가하면 여기서 처리하도록 강제됨
}

// 나쁨: 와일드카드가 새 변형을 숨김
match command {
    Command::Start => start_service(),
    _ => {} // Stop, Restart 및 미래 변형을 조용히 무시
}

트레이트와 제네릭

제네릭을 받고 구체 타입을 반환

// 좋음: 제네릭 입력, 구체 출력
fn read_all(reader: &mut impl Read) -> std::io::Result<Vec<u8>> {
    let mut buf = Vec::new();
    reader.read_to_end(&mut buf)?;
    Ok(buf)
}

// 좋음: 다중 제약을 위한 트레이트 바운드
fn process<T: Display + Send + 'static>(item: T) -> String {
    format!("processed: {item}")
}

동적 디스패치를 위한 트레이트 객체

// 이종 컬렉션이나 플러그인 시스템이 필요할 때 사용
trait Handler: Send + Sync {
    fn handle(&self, request: &Request) -> Response;
}

struct Router {
    handlers: Vec<Box<dyn Handler>>,
}

// 성능이 중요할 때는 제네릭 사용 (모노모피제이션)
fn fast_process<H: Handler>(handler: &H, request: &Request) -> Response {
    handler.handle(request)
}

타입 안전성을 위한 Newtype 패턴

// 좋음: 서로 다른 타입이 인수를 혼동하는 것을 방지
struct UserId(u64);
struct OrderId(u64);

fn get_order(user: UserId, order: OrderId) -> Result<Order> {
    // 사용자 ID와 주문 ID를 실수로 바꿀 수 없음
    todo!()
}

// 나쁨: 인수를 쉽게 바꿔버릴 수 있음
fn get_order_bad(user_id: u64, order_id: u64) -> Result<Order> {
    todo!()
}

구조체와 데이터 모델링

복잡한 생성을 위한 빌더 패턴

struct ServerConfig {
    host: String,
    port: u16,
    max_connections: usize,
}

impl ServerConfig {
    fn builder(host: impl Into<String>, port: u16) -> ServerConfigBuilder {
        ServerConfigBuilder { host: host.into(), port, max_connections: 100 }
    }
}

struct ServerConfigBuilder { host: String, port: u16, max_connections: usize }

impl ServerConfigBuilder {
    fn max_connections(mut self, n: usize) -> Self { self.max_connections = n; self }
    fn build(self) -> ServerConfig {
        ServerConfig { host: self.host, port: self.port, max_connections: self.max_connections }
    }
}

// 사용법: ServerConfig::builder("localhost", 8080).max_connections(200).build()

반복자와 클로저

수동 루프보다 반복자 체인 선호

// 좋음: 선언적, 지연 평가, 조합 가능
let active_emails: Vec<String> = users.iter()
    .filter(|u| u.is_active)
    .map(|u| u.email.clone())
    .collect();

// 나쁨: 명령형 누적
let mut active_emails = Vec::new();
for user in &users {
    if user.is_active {
        active_emails.push(user.email.clone());
    }
}

타입 어노테이션과 함께 collect() 사용

// 다양한 타입으로 수집
let names: Vec<_> = items.iter().map(|i| &i.name).collect();
let lookup: HashMap<_, _> = items.iter().map(|i| (i.id, i)).collect();
let combined: String = parts.iter().copied().collect();

// Result 수집 — 첫 번째 에러에서 단락
let parsed: Result<Vec<i32>, _> = strings.iter().map(|s| s.parse()).collect();

동시성

공유 가변 상태를 위한 Arc<Mutex<T>>

use std::sync::{Arc, Mutex};

let counter = Arc::new(Mutex::new(0));
let handles: Vec<_> = (0..10).map(|_| {
    let counter = Arc::clone(&counter);
    std::thread::spawn(move || {
        let mut num = counter.lock().expect("mutex poisoned");
        *num += 1;
    })
}).collect();

for handle in handles {
    handle.join().expect("worker thread panicked");
}

메시지 전달을 위한 채널

use std::sync::mpsc;

let (tx, rx) = mpsc::sync_channel(16); // 역압력이 있는 경계 채널

for i in 0..5 {
    let tx = tx.clone();
    std::thread::spawn(move || {
        tx.send(format!("message {i}")).expect("receiver disconnected");
    });
}
drop(tx); // 송신자를 닫아 rx 반복자가 종료되도록 함

for msg in rx {
    println!("{msg}");
}

Tokio를 사용한 Async

use tokio::time::Duration;

async fn fetch_with_timeout(url: &str) -> Result<String> {
    let response = tokio::time::timeout(
        Duration::from_secs(5),
        reqwest::get(url),
    )
    .await
    .context("request timed out")?
    .context("request failed")?;

    response.text().await.context("failed to read body")
}

// 동시 태스크 생성
async fn fetch_all(urls: Vec<String>) -> Vec<Result<String>> {
    let handles: Vec<_> = urls.into_iter()
        .map(|url| tokio::spawn(async move {
            fetch_with_timeout(&url).await
        }))
        .collect();

    let mut results = Vec::with_capacity(handles.len());
    for handle in handles {
        results.push(handle.await.unwrap_or_else(|e| panic!("spawned task panicked: {e}")));
    }
    results
}

Unsafe 코드

Unsafe가 허용되는 경우

// 허용: 문서화된 불변성을 가진 FFI 경계 (Rust 2024+)
/// # Safety
/// `ptr`는 초기화된 `Widget`에 대한 유효하고 정렬된 포인터여야 합니다.
unsafe fn widget_from_raw<'a>(ptr: *const Widget) -> &'a Widget {
    // SAFETY: 호출자가 ptr이 유효하고 정렬되었음을 보장함
    unsafe { &*ptr }
}

// 허용: 정확성이 입증된 성능 중요 경로
// SAFETY: 루프 경계로 인해 index가 항상 < len임
unsafe { slice.get_unchecked(index) }

Unsafe가 허용되지 않는 경우

// 나쁨: 빌림 검사기를 우회하기 위해 unsafe 사용
// 나쁨: 편의를 위해 unsafe 사용
// 나쁨: Safety 주석 없이 unsafe 사용
// 나쁨: 관련 없는 타입 간 transmute

모듈 시스템과 크레이트 구조

타입별이 아닌 도메인별로 구성

my_app/
├── src/
│   ├── main.rs
│   ├── lib.rs
│   ├── auth/          # 도메인 모듈
│   │   ├── mod.rs
│   │   ├── token.rs
│   │   └── middleware.rs
│   ├── orders/        # 도메인 모듈
│   │   ├── mod.rs
│   │   ├── model.rs
│   │   └── service.rs
│   └── db/            # 인프라
│       ├── mod.rs
│       └── pool.rs
├── tests/             # 통합 테스트
├── benches/           # 벤치마크
└── Cargo.toml

가시성 — 최소한으로 노출

// 좋음: 내부 공유를 위한 pub(crate)
pub(crate) fn validate_input(input: &str) -> bool {
    !input.is_empty()
}

// 좋음: lib.rs에서 공개 API 재-export
pub mod auth;
pub use auth::AuthMiddleware;

// 나쁨: 모든 것을 pub으로 만들기
pub fn internal_helper() {} // pub(crate) 또는 private이어야 함

도구 통합

필수 명령어

# 빌드 및 검사
cargo build
cargo check              # 코드 생성 없이 빠른 타입 검사
cargo clippy             # 린트 및 제안
cargo fmt                # 코드 포맷팅

# 테스팅
cargo test
cargo test -- --nocapture    # println 출력 표시
cargo test --lib             # 단위 테스트만
cargo test --test integration # 통합 테스트만

# 의존성
cargo audit              # 보안 감사
cargo tree               # 의존성 트리
cargo update             # 의존성 업데이트

# 성능
cargo bench              # 벤치마크 실행

빠른 참조: Rust 관용구

관용구설명
복제하지 말고 빌리기소유권이 필요하지 않으면 복제 대신 &T 전달
불가능한 상태는 표현 불가능하게열거형을 사용하여 유효한 상태만 모델링
unwrap() 대신 ?에러 전파, 라이브러리/프로덕션 코드에서 패닉 금지
검증하지 말고 파싱경계에서 구조화되지 않은 데이터를 타입화된 구조체로 변환
타입 안전성을 위한 Newtype인수 교체를 방지하기 위해 기본 타입을 newtype으로 래핑
루프보다 반복자 선호선언적 체인이 더 명확하고 종종 더 빠름
Result에 #[must_use]호출자가 반환 값을 처리하도록 강제
유연한 소유권을 위한 Cow빌림으로 충분할 때 할당 방지
철저한 매칭비즈니스 크리티컬 열거형에는 와일드카드 _ 금지
최소한의 pub 노출내부 API에는 pub(crate) 사용

피해야 할 안티패턴

// 나쁨: 프로덕션 코드에서 .unwrap()
let value = map.get("key").unwrap();

// 나쁨: 이유를 이해하지 못하고 빌림 검사기를 만족시키기 위해 .clone()
let data = expensive_data.clone();
process(&original, &data);

// 나쁨: &str로 충분할 때 String 사용
fn greet(name: String) { /* 여기는 &str이어야 함 */ }

// 나쁨: 라이브러리에서 Box<dyn Error> 사용 (대신 thiserror 사용)
fn parse(input: &str) -> Result<Data, Box<dyn std::error::Error>> { todo!() }

// 나쁨: must_use 경고 무시
let _ = validate(input); // Result를 조용히 버림

// 나쁨: async 컨텍스트에서 블로킹
async fn bad_async() {
    std::thread::sleep(Duration::from_secs(1)); // 실행기를 블로킹!
    // 대신: tokio::time::sleep(Duration::from_secs(1)).await;
}

기억하세요: 컴파일된다면 아마 올바를 것입니다. 하지만 unwrap()을 피하고, unsafe를 최소화하며, 타입 시스템이 당신을 위해 작동하도록 해야 합니다.

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

accessibility

Design, implement, and audit inclusive digital products using WCAG 2.2 Level AA. Use when building or auditing UI that must meet WCAG 2.2 Level AA, or when reviewing a change for keyboard, contrast, or screen-reader support.

affaan-m/content-engine

Create platform-native content systems for X, LinkedIn, TikTok, YouTube, newsletters, and repurposed multi-platform campaigns. Use when the user wants social posts, threads, scripts, content calendars, or one source asset adapted cleanly across platforms.

affaan-m/ECC

Multi-platform content distribution across X, LinkedIn, Threads, and Bluesky. Adapts content per platform using content-engine patterns. Never posts identical content cross-platform. Use when the user wants to distribute content across social platforms.

affaan-m/fal-ai-media

Unified media generation via fal.ai MCP — image, video, and audio. Covers text-to-image (Nano Banana), text/image-to-video (Seedance, Kling, Veo 3), text-to-speech (CSM-1B), and video-to-audio (ThinkSound). Use when the user wants to generate images, videos, or audio with AI.

affaan-m/manim-video

日本語翻訳:このファイルは manim-video 用の日本語翻訳が必要です

affaan-m/remotion-video-creation

Remotion のベストプラクティス - React で動画を作成する。3D、アニメーション、音声、字幕、チャート、トランジションなどをカバーするドメイン固有の29のルール。

affaan-m/video-editing

AI-assisted video editing workflows for cutting, structuring, and augmenting real footage. Covers the full pipeline from raw capture through FFmpeg, Remotion, ElevenLabs, fal.ai, and final polish in Descript or CapCut. Use when the user wants to edit video, cut footage, create vlogs, or build video content.

agent-architecture-audit

Full-stack diagnostic for agent and LLM applications. Audits the 12-layer agent stack for wrapper regression, memory pollution, tool discipline failures, hidden repair loops, and rendering corruption. Produces severity-ranked findings with code-first fixes. Essential for developers building agent applications, autonomous loops, or any LLM-powered feature. Use when an agent or LLM feature misbehaves and the failing layer is unknown, or before shipping an agent stack.

agent-eval

Head-to-head comparison of coding agents (Claude Code, Aider, Codex, etc.) on custom tasks with pass rate, cost, time, and consistency metrics. Use when choosing between coding agents, or when a change to an agent setup needs measured pass rate, cost, and time rather than an impression.

agent-harness-construction

Design and optimize AI agent action spaces, tool definitions, and observation formatting for higher completion rates. Use when defining or revising an agent

agentic-engineering

Operate as an agentic engineer using eval-first execution, decomposition, and cost-aware model routing. Use when planning or executing engineering work that agents will carry out end to end.

agentic-os

Build persistent multi-agent operating systems on Claude Code. Covers kernel architecture, specialist agents, slash commands, file-based memory, scheduled automation, and state management without external databases. Use when building a persistent multi-agent system on Claude Code with its own memory, commands, and scheduling.

agent-introspection-debugging

Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports. Use when an agent run fails and you need a reproducible diagnosis instead of a retry.

agent-payment-x402

Add x402 payment execution to AI agents with per-task budgets, spending controls, and non-custodial wallets. Supports Base through agentwallet-sdk and X Layer through OKX Payments / OKX Agent Payments Protocol. Use when an agent must pay for something itself and needs per-task budgets, spending controls, and a non-custodial wallet.

agent-self-evaluation

Use after completing any non-trivial task. The agent self-rates its output on 5 axes — accuracy, completeness, clarity, actionability, conciseness — with concrete evidence per criterion. Produces a structured 1-5 scorecard with specific improvement suggestions.

agent-sort

Build an evidence-backed ECC install plan for a specific repo by sorting skills, commands, rules, hooks, and extras into DAILY vs LIBRARY buckets using parallel repo-aware review passes. Use when ECC should be trimmed to what a project actually needs instead of loading the full bundle.

ai-first-engineering

Engineering operating model for teams where AI agents generate a large share of implementation output. Use when setting team process, review gates, or ownership rules for a codebase largely written by agents.

ai-regression-testing

Regression testing strategies for AI-assisted development. Sandbox-mode API testing without database dependencies, automated bug-check workflows, and patterns to catch AI blind spots where the same model writes and reviews code. Use when adding regression coverage to AI-assisted code, or when the same model both wrote and reviewed a change.

android-clean-architecture

Clean Architecture patterns for Android and Kotlin Multiplatform projects — module structure, dependency rules, UseCases, Repositories, and data layer patterns. Use when structuring modules, layers, or data flow in an Android or KMP project.

angular-developer

Generates Angular code and provides architectural guidance. Trigger when creating projects, components, or services, or for best practices on reactivity (signals, linkedSignal, resource), forms, dependency injection, routing, SSR, accessibility (ARIA), animations, styling (component styles, Tailwind CSS), testing, or CLI tooling.

관련 스킬