Communitygithub.com

Rust 개발 패턴

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

Rust 개발 패턴란 무엇인가요?

이 스킬은 Rust 개발자가 안전하고 성능이 좋으며 유지보수하기 쉬운 애플리케이션을 작성할 수 있도록 돕는 포괄적인 패턴과 모범 사례를 제공합니다. 여섯 가지 핵심 영역(소유권과 빌림, Result/? 기반 에러 처리(라이브러리는 thiserror, 애플리케이션은 anyhow), 열거형과 철저한 패턴 매칭, 트레이트와 제네릭을 통한 제로-코스트 추상화, Arc<Mutex<T>>/채널/async/await를 사용한 안전한 동시성, 도메인 기반의 최소 pub 노출)을 다룹니다. 이 스킬은 Claude Code, Cursor, Codex 등 다양한 AI 어시스턴트에서 사용할 수 있으며, Rust 코드 작성, 리뷰, 리팩토링 시 적용됩니다. SKILL.md 문서는 코드 예제, 핵심 원칙, 안티 패턴 및 관련 CLI 명령어(cargo check, clippy, fmt, test 등)를 포함하여 개발자가 실시간으로 지침을 따를 수 있도록 합니다. 주요 철학은 '컴파일되면 아마도 맞다' 이지만, unwrap()을 피하고 unsafe를 최소화하며 타입 시스템을 적극 활용해야 한다는 점을 강조합니다.

지원 대상~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/everything-claude-code/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),
    }
}

철저한 매칭 — 비즈니스 로직에는 포괄적인 _ 패턴 사용 금지

// 좋음: 모든 변형을 명시적으로 처리
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)
}

타입 안전성을 위한 뉴타입 패턴

// 좋음: 서로 다른 타입이 인수 섞임 방지
struct UserId(u64);
struct OrderId(u64);

fn get_order(user: UserId, order: OrderId) -> Result<Order> {
    // 실수로 user와 order 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()

반복자와 클로저

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

// 좋음: 선언적, 게으름(lazy), 조합 가능
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();

// Results 수집 — 첫 에러에서 단락(short-circuit)
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); // 수신자 반복자가 종료되도록 송신자 닫기

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

Tokio를 사용한 비동기

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 Code)

Unsafe가 허용되는 경우

// 허용: 문서화된 불변 조건을 가진 FFI 경계
/// # 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() 대신 ?에러를 전파하고 라이브러리/프로덕션 코드에서 패닉 금지
검증하지 말고 파싱비정형 데이터를 경계에서 타입화된 구조체로 변환
타입 안전성을 위한 뉴타입인수 바꿈을 방지하기 위해 기본 타입을 뉴타입으로 감싸기
루프보다 반복자 선호선언적 체인이 더 명확하고 종종 더 빠름
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 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/claude-api

Anthropic Claude API patterns for Python and TypeScript. Covers Messages API, streaming, tool use, vision, extended thinking, batches, prompt caching, and Claude Agent SDK. Use when building applications with the Claude API or Anthropic SDKs.

affaan-m/everything-claude-code

End-to-end marketing campaign planning and execution. Covers audience research, positioning, campaign angle definition, landing page copy, email sequences, social posts, ad copy, short-form video scripts, and content calendars. Use as the orchestration layer for multi-channel product launches. Use when planning or executing a multi-channel product launch, or producing landing page, email, social, or ad copy.

affaan-m/everything-claude-code

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/everything-claude-code-conventions

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/frontend-design

Create distinctive, production-grade frontend interfaces with high design quality. Use when the user asks to build web components, pages, or applications and the visual direction matters as much as the code quality.

affaan-m/gget

gget CLI and Python workflow for quick genomic database queries, sequence lookup, BLAST-style searches, enrichment checks, and reproducible bioinformatics evidence logs.

affaan-m/literature-review

Systematic literature-review workflow for academic, biomedical, technical, and scientific topics, including search planning, source screening, synthesis, citation checks, and evidence logging.

affaan-m/motion-ui

Production-ready UI motion system for React/Next.js. Use when implementing animations, transitions, or motion patterns.

affaan-m/project-guidelines-example

Example project-specific skill template based on a real production application.

affaan-m/pubmed-database

Direct PubMed and NCBI E-utilities search workflows for biomedical literature, MeSH queries, PMID lookup, citation retrieval, and API-backed literature monitoring.

affaan-m/scholar-evaluation

Structured scholarly-work evaluation for papers, proposals, literature reviews, methods sections, evidence quality, citation support, and research-writing feedback.

affaan-m/uspto-database

USPTO patent and trademark data workflow for official record lookup, PatentSearch queries, TSDR checks, assignment data, and reproducible IP research logs.

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.

관련 스킬