npx claudepluginhub sam42-lab/everything-claude-code-krThis skill uses the workspace's default tool permissions.
안전하고 성능이 뛰어나며 유지보수가 쉬운 애플리케이션을 구축하기 위한 관용적인(idiomatic) 러스트 패턴과 모범 사례입니다.
Mandates invoking relevant skills via tools before any response in coding sessions. Covers access, priorities, and adaptations for Claude Code, Copilot CLI, Gemini CLI.
Share bugs, ideas, or general feedback.
안전하고 성능이 뛰어나며 유지보수가 쉬운 애플리케이션을 구축하기 위한 관용적인(idiomatic) 러스트 패턴과 모범 사례입니다.
이 스킬은 6가지 핵심 영역에서 관용적인 러스트 컨벤션을 적용합니다: 컴파일 타임에 데이터 경합을 방지하는 소유권 및 빌림(ownership and borrowing), 라이브러리용 thiserror 및 애플리케이션용 anyhow를 사용한 Result/? 에러 전파, 유효하지 않은 상태를 표현 불가능하게 만드는 열거형(enums) 및 철저한 패턴 매칭, 제로 비용 추상화를 위한 트레이트(traits) 및 제네릭, Arc<Mutex<T>>, 채널, async/await를 통한 안전한 동시성, 그리고 도메인별로 조직화된 최소한의 pub 노출입니다.
러스트의 소유권 시스템은 컴파일 타임에 데이터 경합과 메모리 버그를 방지합니다.
// 좋음: 소유권이 필요하지 않을 때는 참조 전달
fn process(data: &[u8]) -> usize {
data.len()
}
// 좋음: 데이터를 저장하거나 소비해야 할 때만 소유권 획득
fn store(data: Vec<u8>) -> Record {
Record { payload: data }
}
// 나쁨: 빌림 검사기를 피하기 위해 불필요하게 복제(clone)
fn process_bad(data: &Vec<u8>) -> usize {
let cloned = data.clone(); // 낭비 — 그냥 빌려오면 됨
cloned.len()
}
use std::borrow::Cow;
fn normalize(input: &str) -> Cow<'_, str> {
if input.contains(' ') {
Cow::Owned(input.replace(' ', "_"))
} else {
Cow::Borrowed(input) // 변경이 필요 없을 때는 제로 비용 빌림
}
}
// 좋음: 컨텍스트와 함께 에러 전파
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)
}
// 나쁨: 에러 발생 시 패닉(panic) 유발
fn load_config_bad(path: &str) -> Config {
let content = std::fs::read_to_string(path).unwrap(); // 패닉 발생!
toml::from_str(&content).unwrap()
}
// 라이브러리 코드: 구조화된 타입 에러
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(())
}
// 좋음: 컴비네이터 체인
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),
}
}
// 좋음: 모든 배리언트(variant)를 명시적으로 처리
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)
}
// 좋음: 여러 제약 조건을 위한 트레이트 바운드(bounds)
fn process<T: Display + Send + 'static>(item: T) -> String {
format!("processed: {item}")
}
// 이기종(heterogeneous) 컬렉션이나 플러그인 시스템이 필요할 때 사용
trait Handler: Send + Sync {
fn handle(&self, request: &Request) -> Response;
}
struct Router {
handlers: Vec<Box<dyn Handler>>,
}
// 성능(단형성화, monomorphization)이 필요할 때는 제네릭 사용
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> {
// 사용자 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());
}
}
// 다양한 타입으로 수집
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 수집 — 첫 번째 에러 발생 시 단락(short-circuit) 평가
let parsed: Result<Vec<i32>, _> = strings.iter().map(|s| s.parse()).collect();
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); // 백프레셔(backpressure)가 있는 유한 채널
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}");
}
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
}
// 허용됨: 불변 조건(invariants)이 문서화된 FFI 경계 (Rust 2024+)
/// # Safety
/// `ptr`은 유효하고 정렬된 초기화된 `Widget` 포인터여야 합니다.
unsafe fn widget_from_raw<'a>(ptr: *const Widget) -> &'a Widget {
// SAFETY: 호출자가 ptr의 유효성과 정렬을 보장함
unsafe { &*ptr }
}
// 허용됨: 정확성이 증명된 성능 임계 경로
// SAFETY: 루프 제약 조건에 의해 인덱스는 항상 len보다 작음
unsafe { slice.get_unchecked(index) }
// 나쁨: 빌림 검사기를 우회하기 위해 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 재노출
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 # 벤치마크 실행
| 관용구 | 설명 |
|---|---|
| 복제 대신 빌림 | 소유권이 필요한 경우가 아니면 복제 대신 &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)); // 실행기(executor)를 차단함!
// 대신 사용: tokio::time::sleep(Duration::from_secs(1)).await;
}
기억하세요: 컴파일이 된다면 아마 맞을 것입니다 — 단, unwrap()을 피하고, unsafe를 최소화하며, 타입 시스템이 당신을 위해 일하게 할 때만 해당됩니다.