From dev-tools
모던 Rust 개발 컨벤션을 강제하는 스킬. cargo(빌드/패키지 관리), clippy(린터), rustfmt(포매터), Rust 2021+ edition 문법을 사용한다. Rust 파일 생성, 프로젝트 초기화, Cargo.toml 수정, 의존성 추가, Rust 코드 작성, API 서비스 구축 등 Rust 관련 작업 시 반드시 이 스킬을 사용할 것. Makefile 기반 빌드, 수동 의존성 관리, nightly-only 기능 남용을 지양하고 cargo 표준 워크플로우를 따른다.
npx claudepluginhub bahamoth/claude-marketplace --plugin dev-toolsThis skill uses the workspace's default tool permissions.
이 스킬은 Rust 표준 툴체인 중심의 모던 개발을 강제한다. Claude가 기본적으로 제안할 수 있는 비표준 패턴 대신, cargo/clippy/rustfmt 기반의 관용적(idiomatic) Rust 워크플로우를 사용한다.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Guides MCP server integration in Claude Code plugins via .mcp.json or plugin.json configs for stdio, SSE, HTTP types, enabling external services as tools.
이 스킬은 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 비동기 코드, 동시성 패턴 작성 시 |