From dev-tools
모던 Rust 개발 컨벤션을 강제하는 스킬. cargo(빌드/패키지 관리), clippy(린터), rustfmt(포매터), Rust 2021+ edition 문법을 사용한다. Rust 파일 생성, 프로젝트 초기화, Cargo.toml 수정, 의존성 추가, Rust 코드 작성, API 서비스 구축 등 Rust 관련 작업 시 반드시 이 스킬을 사용할 것. Makefile 기반 빌드, 수동 의존성 관리, nightly-only 기능 남용을 지양하고 cargo 표준 워크플로우를 따른다.
How this skill is triggered — by the user, by Claude, or both
Slash command
/dev-tools:rust-devThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
이 스킬은 Rust 표준 툴체인 중심의 모던 개발을 강제한다. Claude가 기본적으로 제안할 수 있는 비표준 패턴 대신, cargo/clippy/rustfmt 기반의 관용적(idiomatic) Rust 워크플로우를 사용한다.
이 스킬은 Rust 표준 툴체인 중심의 모던 개발을 강제한다. Claude가 기본적으로 제안할 수 있는 비표준 패턴 대신, cargo/clippy/rustfmt 기반의 관용적(idiomatic) Rust 워크플로우를 사용한다.
unsafe는 명확한 이유 없이 사용하지 않는다. clone()을 소유권 문제 회피 수단으로 남용하지 않는다.edition = "2021"을 기본으로 설정한다.unwrap()/expect()는 테스트와 초기화 코드에서만 허용한다. 라이브러리는 커스텀 에러 타입, 애플리케이션은 anyhow/thiserror를 사용한다.| 금지 | 대체 | 이유 |
|---|---|---|
unwrap() (프로덕션 코드) | ? 연산자 + 적절한 에러 타입 | 패닉 대신 에러 전파가 안전하고 합성 가능 |
clone() 남용 | 참조, 라이프타임, Cow<T> | 불필요한 힙 할당 방지, 소유권 모델 활용 |
unsafe (명확한 이유 없이) | 안전한 추상화 | unsafe는 FFI, 성능 최적화 등 명확한 사유 필요 |
println! (로깅 목적) | tracing / log 크레이트 | 구조화된 로깅이 프로덕션에서 관찰 가능성 제공 |
| Makefile 기반 빌드 | cargo build / cargo xtask | cargo가 빌드 의존성과 기능 플래그를 통합 관리 |
Box<dyn Error> (라이브러리) | thiserror 커스텀 에러 | 호출자가 에러를 패턴 매칭으로 처리 가능 |
String (에러 메시지용) | thiserror / anyhow | 타입 기반 에러가 합성 가능하고 컨텍스트 제공 |
| nightly-only 기능 | stable 대안 | 프로덕션 코드는 stable 컴파일러에서 빌드되어야 함 |
# 바이너리 프로젝트
cargo init <project-name>
cd <project-name>
# 라이브러리 프로젝트
cargo init --lib <project-name>
모노레포나 다중 크레이트 프로젝트는 워크스페이스를 사용한다.
# 루트 Cargo.toml
[workspace]
resolver = "2"
members = ["crates/*"]
[workspace.package]
edition = "2021"
rust-version = "1.75"
[workspace.dependencies]
serde = { version = "1", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
project-name/
├── Cargo.toml
├── Cargo.lock # 바이너리: 커밋, 라이브러리: .gitignore
├── src/
│ ├── main.rs # 바이너리 진입점
│ ├── lib.rs # 라이브러리 루트
│ ├── error.rs # 커스텀 에러 타입
│ └── modules/
│ └── mod.rs
├── tests/ # 통합 테스트
│ └── integration_test.rs
├── benches/ # 벤치마크
│ └── benchmark.rs
└── examples/ # 예제 코드
└── basic.rs
Cargo.toml 전체 템플릿은
references/cargo-toml-templates.md를 참조하라.
&self/&mut self/self 중 최소 권한 원칙에 따라 선택한다.String 대신 &str을 파라미터로 받는다 (소유권이 필요한 경우만 String).Vec<T> 대신 &[T]를 파라미터로 받는다 (소유권이 필요한 경우만 Vec<T>).impl Trait 또는 where 절로 표현한다.// BAD — 불필요한 소유권, unwrap 남용, 에러 무시
fn process_data(data: String) -> String {
let parsed: Value = serde_json::from_str(&data).unwrap();
let result = parsed["name"].as_str().unwrap().to_string();
result
}
// GOOD — 참조 활용, 적절한 에러 처리
fn process_data(data: &str) -> Result<String> {
let parsed: Value = serde_json::from_str(data)?;
let name = parsed["name"]
.as_str()
.context("missing 'name' field")?;
Ok(name.to_owned())
}
프로젝트 유형에 따라 에러 처리 전략을 선택한다.
라이브러리 크레이트
→ thiserror 기반 커스텀 에러 enum
이유: 호출자가 에러를 패턴 매칭으로 세밀하게 처리 가능
애플리케이션 (바이너리)
→ anyhow::Result + .context()
이유: 에러 체이닝과 백트레이스로 디버깅 용이
혼합 (lib + bin)
→ lib에 thiserror, bin에 anyhow
// 라이브러리: thiserror
use thiserror::Error;
#[derive(Debug, Error)]
pub enum AppError {
#[error("database error: {0}")]
Database(#[from] sqlx::Error),
#[error("validation failed: {field}")]
Validation { field: String },
#[error("not found: {0}")]
NotFound(String),
}
// 애플리케이션: anyhow
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)
.context("invalid config format")?;
Ok(config)
}
에러 처리 상세 패턴은
references/error-handling-guide.md를 참조하라.
clippy가 린터, rustfmt가 포매터를 담당한다.
프로젝트 루트에 clippy.toml 또는 Cargo.toml의 [lints.clippy]에 설정한다.
# Cargo.toml
[lints.clippy]
# 경고 → 에러 승격
unwrap_used = "deny"
expect_used = "warn"
panic = "deny"
# 관용적 Rust 강제
needless_pass_by_value = "warn"
cloned_instead_of_copied = "warn"
redundant_closure_for_method_calls = "warn"
# pedantic 중 유용한 것들
doc_markdown = "warn"
missing_errors_doc = "warn"
must_use_candidate = "warn"
[lints.rust]
unsafe_code = "forbid"
cargo clippy --all-targets --all-features -- -D warnings # 린트
cargo fmt --all -- --check # 포맷 체크
cargo fmt --all # 포맷 적용
전체 clippy 설정과 lint 그룹은
references/clippy-config.md를 참조하라.
# 의존성 추가
cargo add serde --features derive
cargo add tokio --features full
# 개발 의존성 추가
cargo add --dev criterion
cargo add --dev tokio-test
# 빌드 의존성
cargo add --build cc
# 의존성 제거
cargo remove <crate>
# 의존성 업데이트
cargo update # 모든 의존성
cargo update -p serde # 특정 크레이트
# 보안 감사
cargo install cargo-audit
cargo audit
Cargo.lock을 git에 커밋한다 (재현 가능한 빌드 보장).Cargo.lock을 .gitignore에 추가한다 (소비자가 자체 락 파일 관리).[features]
default = []
full = ["serde", "async"]
serde = ["dep:serde"]
async = ["dep:tokio"]
feature 플래그로 선택적 의존성을 관리한다. default는 비워두고 사용자가 명시적으로 활성화하게 한다.
# 전체 테스트
cargo test
# 특정 테스트
cargo test test_name
# 특정 모듈
cargo test module_name::
# doc 테스트 포함
cargo test --doc
# 릴리스 프로필로 테스트
cargo test --release
# 출력 표시
cargo test -- --nocapture
// 유닛 테스트: 같은 파일 하단
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_valid_input() {
let result = parse("hello").unwrap();
assert_eq!(result, Expected::Value("hello"));
}
#[test]
fn test_parse_empty_input() {
let result = parse("");
assert!(result.is_err());
}
}
// 통합 테스트: tests/ 디렉토리
// tests/integration_test.rs
use my_crate::public_api;
#[test]
fn test_full_workflow() {
let result = public_api::process("input");
assert!(result.is_ok());
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_async_operation() {
let result = fetch_data("url").await.unwrap();
assert!(!result.is_empty());
}
}
비동기가 필요한 경우 tokio를 기본 런타임으로 사용한다.
[dependencies]
tokio = { version = "1", features = ["full"] }
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// ...
Ok(())
}
비동기 패턴 상세는
references/async-patterns.md를 참조하라.
CLI가 필요한 경우 clap을 사용한다. derive 매크로로 선언적으로 CLI를 정의한다.
cargo add clap --features derive
use clap::Parser;
#[derive(Parser)]
#[command(name = "mytool", about = "파일 처리 도구")]
struct Cli {
/// 입력 파일 경로
input: String,
/// 출력 디렉토리
#[arg(short, long, default_value = "output")]
output_dir: String,
/// 상세 출력
#[arg(short, long)]
verbose: bool,
}
fn main() -> anyhow::Result<()> {
let cli = Cli::parse();
// ...
Ok(())
}
HTTP 서비스가 필요한 경우 axum을 기본 프레임워크로 사용한다.
cargo add axum tokio --features tokio/full
cargo add serde --features derive
cargo add tower-http --features cors,trace
use axum::{routing::get, Json, Router};
use serde::Serialize;
#[derive(Serialize)]
struct Health {
status: String,
}
async fn health() -> Json<Health> {
Json(Health { status: "ok".into() })
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let app = Router::new().route("/health", get(health));
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?;
axum::serve(listener, app).await?;
Ok(())
}
데이터 직렬화/역직렬화는 serde를 사용한다.
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct User {
id: u64,
user_name: String,
#[serde(skip_serializing_if = "Option::is_none")]
email: Option<String>,
}
코드를 커밋하기 전에 다음을 실행한다.
cargo fmt --all # 포맷
cargo clippy --all-targets --all-features -- -D warnings # 린트
cargo test # 테스트
cargo doc --no-deps # 문서 빌드 확인
모든 명령이 에러 없이 통과해야 한다.
필요할 때 다음 파일을 읽어 상세 설정을 확인하라.
| 파일 | 로드 시점 |
|---|---|
references/cargo-toml-templates.md | 프로젝트 초기화 또는 Cargo.toml 수정 시 |
references/clippy-config.md | clippy 설정 추가/변경 또는 lint 에러 해결 시 |
references/error-handling-guide.md | 에러 타입 설계 또는 에러 처리 패턴 선택 시 |
references/async-patterns.md | tokio 비동기 코드, 동시성 패턴 작성 시 |
npx claudepluginhub bahamoth/claude-marketplace --plugin dev-toolsEnforces strict Rust standards including FAIL FAST error handling, workspace architecture, dependency management with version scripts, and patterns for code reviews, projects, and compilation fixes.
Guides Rust developers through day-1 decisions: ownership, error handling, async, and crate choices (tokio, axum, serde). Useful when starting a new Rust project or learning Rust from another language.
Scaffolds new Rust projects with best practices for Cargo.toml, linting (clippy, rustfmt), CI pipelines, and workspace organization.