소스 정보
- 저장소
- irahardianto/awesome-agv
- 최근 소스 활동
- 2026년 7월 30일 14:18
- 감지된 SKILL.md 언어
- 영어
- 스타
- 150
- 포크
- 48
설치 방법
기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.
소스 파일 검토
설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.
메뉴
기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.
설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
직접 명령은 검토 Prompt를 거치지 않습니다. 실행하기 전에 소스를 확인하세요.
npx skills add https://github.com/irahardianto/awesome-agv --skill rust-idioms명령은 한 줄로 유지됩니다. 복사하기 전에 가로로 스크롤해 전체 내용을 확인하세요.
로컬 사본을 원하시나요? SkillsMP에서 현재 제공할 수 있는 파일을 다운로드하세요.
Structured logging implementation patterns: log levels, mandatory context fields (correlationId, userId, duration), security (PII scrubbing), and per-language library choices (Go slog, TypeScript pino, Python structlog). Load when implementing logging in any operation entry point. Prerequisite: logging-and-observability-mandate.md.
Python type hints, Protocols, Pydantic, async/await, pytest, ruff, mypy strict.
Go stdlib, error wrapping, interfaces, goroutines, table-driven tests, gofumpt.
SOC 직업 분류 기준
SKILL.md 표시 중
| name | rust-idioms |
| description | Rust ownership, tokio, thiserror/anyhow, Clippy pedantic, unsafe, lifetimes. |
| paths | ["**/*.rs","**/Cargo.toml"] |
Rust's type system and ownership model are your primary tools for correctness. Lean into the compiler — it is your strongest ally. Write code that is idiomatic, safe, and expressive.
Scope: This file covers Rust-specific coding idioms. For file layout, see
references/project-structure.md(in this skill). For detailed safety, SAST security invariants, and performance anti-patterns, seereferences/rust-patterns-and-anti-patterns.md(in this skill). For Rust test naming and conventions, see §Testing below; for universal testing principles, see@.agents/rules/testing-strategy.md. For logging library choice and setup, see@.agents/skills/logging-implementation/SKILL.md.
Default to the latest stable Rust. As of July 2026, this is Rust 1.97. All guidance in this skill assumes latest stable features. When creating new projects, set
rust-versioninCargo.tomlto prevent builds on outdated toolchains.
[package]
edition = "2024"
rust-version = "1.97"
Key version milestones that affect this skill:
async fn in traits (no async_trait crate needed for static dispatch). This milestone is the single source of truth for the async-trait crate policy:
impl MyTrait return params or generic T: MyTrait bounds use native async fn in traits; no crate required and zero dispatch overhead.async-trait crate only when dynamic dispatch via dyn Trait is explicitly required — e.g. Box<dyn MyTrait>, Arc<dyn MyTrait> for runtime polymorphism, object-safe trait objects, or storing heterogeneous trait impls in a collection.async-trait as a default dependency just for ergonomics — it adds a heap allocation and dynamic dispatch cost. Add it only for the specific crates/traits that need dyn dispatch.[workspace.lints])Mutex::new() in const context (no OnceCell wrapper needed)For recommended crate versions and starter
Cargo.toml, seereferences/recommended-dependencies.md.
Prefer borrowing (&T, &mut T) over cloning
.clone() to silence the borrow checker without a // CLONE: comment explaining whyCow<'_, T> when a function may or may not need ownership&str over String in function parameters, &[T] over Vec<T>Minimize owned data in structs
String, Vec<T>) when the struct must outlive its inputsAvoid unnecessary Arc<Mutex<T>>
tokio::sync::mpsc)RwLock over MutexArc<T> without a lockRespect the Copy / Clone boundary:
.clone() on types that implement Copy (e.g., i32, f64, bool, char, usize, Option<CopyType>).clone() is misleading and suggests heap allocationCopy = bitwise copy (stack only); Clone = potentially expensive deep copy// ❌ Misleading — usize implements Copy
let count = other_count.clone();
// ✅ Implicit copy — clear and correct
let count = other_count;
Use the ? operator for propagation — never unwrap() in production code
unwrap() and expect() are acceptable only in:
#[test], #[tokio::test])// SAFETY: comment)main() function with clear error messages via expect("reason")Choose error crates by context:
| Context | Crate | Reason |
|---|---|---|
| Library crates | thiserror | Typed, matchable errors. Callers need to handle specific variants. |
| Web service HTTP errors | thiserror | AppError enum must implement IntoResponse — typed variants required. |
| Service/domain layer errors | thiserror | Domain errors need structured variants for logging and client responses. |
| Application glue / scripts / CLI | anyhow | Error type doesn't matter; ergonomic propagation is all you need. |
Web service rule: Use
thiserrorforAppError(HTTP handler errors) and domain errors.anyhow::Errordoes NOT implementIntoResponseand cannot be returned from Axum handlers. Useanyhowonly in non-HTTP utility code (scripts, migration runners, CLI entrypoints) where errors are printed, not sent over the wire.The idiomatic pattern is
thiserrorfor typed variants +#[from] anyhow::Erroras the catch-allInternalvariant inAppError. Seeaxum-idioms/SKILL.md§Error Handling for the complete pattern.Never add
anyhowas a dependency to library crates — it leaks a concrete error type into your public API.
Error type design:
// ✅ Good — typed, matchable errors
#[derive(Debug, thiserror::Error)]
pub enum PathfinderError {
#[error("file not found: {path}")]
FileNotFound { path: PathBuf },
#[error("AST parse failed: {0}")]
ParseError(String),
#[error(transparent)]
Io(#[from] std::io::Error),
}
// ❌ Bad — stringly-typed, unmatchable
fn do_thing() -> Result<(), String> { ... }
// ✅ Use #[must_use] on functions returning non-Result types that callers must handle
#[must_use]
pub fn compute_checksum(data: &[u8]) -> u64 { ... }
// ℹ️ Result<T, E> already has #[must_use] in std — adding it to Result-returning
// functions is redundant. The compiler warns on unused Result values automatically.
pub fn create_task(req: CreateTaskRequest) -> Result<Task, TaskError> { ... }
Use lazy evaluation for fallback values:
unwrap_or_else(|| expr) instead of unwrap_or(expr) when the fallback involves a function callexpect messages should be string literals, not format!() callsmap_or_else instead of map_or when either branch involves computation// ❌ Eager — default_value() is called even when result is Ok
let val = result.unwrap_or(default_value());
let msg = result.expect(&format!("failed for {id}"));
// ✅ Lazy — default_value() only called when needed
let val = result.unwrap_or_else(|_| default_value());
let msg = result.unwrap_or_else(|e| panic!("failed for {id}: {e}"));
Use tokio as the async runtime
#[tokio::main] or #[tokio::test]tokio::spawn for concurrent tasks, not std::thread::spawntokio::select! for racing futures, not manual pollingCancellation safety:
tokio::sync::mpsc over tokio::sync::broadcast unless fan-out is neededasync fn that holds resources across .awaittokio_util::sync::CancellationToken for graceful shutdownBlocking operations:
tokio::task::spawn_blocking for CPU-heavy or blocking worktokio::fs instead of std::fs inside async functionsUse tracing instead of log for all structured diagnostics in async applications:
tracing is span-aware — log entries inherit context from parent spans (correlation IDs, request metadata)log is fire-and-forget with no span concept — unsuitable for async where context flows across .await boundaries#[tracing::instrument] on async functions to automatically create spans with function arguments@.agents/skills/logging-implementation/SKILL.md §Rust for the full setupZero unsafe blocks unless in FFI boundaries
unsafe block must have a // SAFETY: comment explaining the invariantMinimize unsafe surface area:
unsafe in a safe wrapper functionunsafe wrappersNever use unsafe to bypass the borrow checker — restructure the code instead
Prefer '_ lifetime elision when possible
'a for single lifetime parameters, descriptive names ('input, 'query) for multipleKeep generic bounds simple:
impl Trait in argument position for simple caseswhere clauses for complex bounds — never inline complex bounds in <...>Avoid lifetime gymnastics:
ArcBuilder pattern for types with many optional fields:
Self from builder methods for chainingbuild() returns Result<T, BuildError>, not TNewtype pattern for domain types:
struct UserId(u64), not bare u64Deref only when the newtype truly "is-a" the inner typeTypestate pattern for state machines:
From/Into conversions:
From<A> for B (never Into directly)impl From<X> for Error with thiserror's #[from] attributePrefer T::new() over Default::default() for known types:
Vec::new(), String::new(), HashMap::new() — explicit, readable, idiomaticDefault::default() in generic contexts where T: Default bounds are neededDefault::default() in struct update syntax: MyStruct { field: value, ..Default::default() }// ✅ Idiomatic — explicit constructor for known types
let items: Vec<String> = Vec::new();
let name = String::new();
let map: HashMap<, > = HashMap::();
= ::();
<T: >() T {
T::()
}
= ServerConfig {
port: ,
..::()
};
Test organization (Rust-specific — differs from Go/TS):
For the authoritative test layout rules (unit vs integration vs e2e placement,
#[cfg(test)]conventions,tests/common/mod.rspattern,#[tokio::test]usage), seereferences/project-structure.md§Testing Layout. The rules are co-located there to stay in sync with the directory layout they describe.
Test naming: fn test_<function>_<scenario>_<expected>() (snake_case)
Assertions:
assert_eq! / assert_ne! over assert!(a == b) — better error messagesassert!(matches!(result, Ok(_))) for enum variant checkingassert!(true) or assert!(false):
assert!(false) / debug_assert!(false) → use unreachable!("reason") or panic!("reason")assert!(true) → remove entirely (it tests nothing)Property testing: Use proptest (preferred) or quickcheck for functions with wide input spaces. proptest is preferred for its superior strategy composability, automatic shrinking, and more expressive generators.
Test coverage is non-negotiable for new code:
pub fn, pub struct method, and impl block MUST have at least one testif/else, match arm, error path) MUST be exercised by a testcargo tarpaulin or cargo llvm-cov to verify coverage locally before committing# Quick coverage check during development
cargo tarpaulin --workspace --skip-clean --out stdout
cargo llvm-cov --workspace --lcov --output-path lcov.info
cargo check for fast iteration during development
cargo check: type-checks without producing a binary — fastest feedback loopcargo clippy: includes cargo check plus lint rules — use before committingcargo build: only when you need the actual binary/library artifactcargo build during TDD cycles — it is significantly slower than cargo checkcargo clippy must pass with zero warnings before any commit
Clippy suppression policy — fix the code, don't silence the lint:
NEVER suppress these lints — they signal structural problems that must be fixed:
| Lint | What It Signals | What To Do Instead |
|---|---|---|
too_many_lines | Function is monolithic | Decompose into smaller functions (see Idiomatic Patterns §7) |
cognitive_complexity | Too many branches/nesting | Flatten with early returns, extract match arms |
too_many_arguments | Function has too many params | Introduce a params/config struct or builder |
type_complexity | Nested generics are unreadable | Create a type alias or newtype wrapper |
struct_excessive_bools | Struct has too many boolean fields | Replace with an enum, bitflags, or config sub-struct |
large_enum_variant | Enum variant is disproportionately large | Box the large variant's payload |
Decomposition strategies (use INSTEAD of #[allow]):
// ❌ FORBIDDEN — agent took the lazy path
#[allow(clippy::too_many_lines)]
fn process_request(req: &Request) -> Result<Response> {
}
(req: &Request) <Response> {
= (req)?;
= (&validated)?;
(&enriched)
}
(host: &, port: , tls: , timeout: ,
max_conn: , log_level: &, cert: &Path) Server { ... }
{
host: ,
port: ,
tls: ,
timeout: Duration,
max_connections: ,
log_level: Level,
cert_path: PathBuf,
}
(config: ServerConfig) Server { ... }
() HashMap<, < (&Request) Pin<< Future<Output = Response>>>>> { ... }
= < (&Request) Pin<< Future<Output = Response>>>>;
() HashMap<, HandlerFn> { ... }
Cargo.toml — use dep = "1" not dep = "*"cargo audit to check for known vulnerabilitiesFeatures must be additive — enabling a feature must only add functionality, never change or remove existing behavior
Use dep: syntax for optional dependencies to keep the feature namespace clean:
[features]
default = ["json"]
json = ["dep:serde_json"] # ✅ Uses dep: prefix
grpc = ["dep:tonic"] # ✅ Feature doesn't auto-expose dep as feature
Guard feature-gated code with #[cfg(feature = "...")]:
#[cfg(feature = "grpc")]
pub mod grpc_handler;
Test feature combinations in CI using cargo-hack:
cargo hack test --feature-powerset --depth 2
Never use features for mutually exclusive backends — use traits and runtime selection instead
Never use string literals directly in std::env::var():
// ❌ Bug-prone — typos are silent, scattered across codebase
let port = std::env::var("PATHFINDER_PORT").unwrap_or("3000".into());
let host = std::env::var("PATHFNDER_HOST").unwrap_or("localhost".into()); // typo!
// ✅ Safe — constants catch typos at compile time, single source of truth
mod env_keys {
pub const PORT: &str = "PATHFINDER_PORT";
pub const HOST: &str = "PATHFINDER_HOST";
}
let port = std::env::var(env_keys::PORT).unwrap_or_else(|_| "3000".into());
let host = std::env::var(env_keys::HOST).unwrap_or_else(|_| "localhost".into());
Prefer structured config parsing over scattered env::var calls:
Key safety rules (non-negotiable):
unsafe without a // SAFETY: comment documenting the invarianttransmute across types of different sizes or with different validity invariantsas casts with explicit bounds checks — as silently truncatestokio::task::spawn_blocking for CPU-heavy or synchronous I/O work inside async contextsFor the full catalog of safety invariants, SAST patterns, concurrency rules (lock guards, atomics, async), memory safety (double indirection, transmute, pointer casts), collection best practices (retain, deterministic iteration), and security (TOCTOU, path traversal, cookie flags, CSP), see
references/rust-patterns-and-anti-patterns.md. Load it before writing any unsafe code, concurrent code, or I/O handling code.For performance patterns (arena allocation, SmallVec, zero-copy parsing, Cow, pre-sized collections, benchmarking), see
perf-optimization/languages/rust.md.
Use stdlib convenience methods — avoid manual reimplementations:
str.split_once(pat) instead of manual splitn(2, pat) + indexinga.min(b) / a.max(b) / a.clamp(lo, hi) instead of match a.cmp(&b) { ... }Ordering::then() / Ordering::then_with() for multi-field comparisons// ❌ Manual reimplementation
let parts: Vec<&str> = s.splitn(2, ':').collect();
let key = parts[0];
let value = parts.get(1).unwrap_or(&"");
// ✅ Idiomatic — clearer intent, less code
let (key, value) = s.split_once(':').unwrap_or((s, ""));
// ❌ Redundant match over Ordering
match a.cmp(&b) {
Ordering::Less | Ordering::Equal => a,
Ordering::Greater => b,
}
// ✅ Direct
a.min(b)
// ❌ Redundant let-binding
let result = compute_something();
result
// ✅ Return directly
compute_something()
Keep function complexity low (cyclomatic complexity < 10):
match arms into named helper functionsif !condition { return Err(...) }) to flatten nesting// ❌ High complexity — nested match + conditionals
fn process(input: &Input) -> Result<Output> {
match input.kind {
Kind::A => {
if input.flag {
// 20 lines...
} else {
// 20 lines...
}
}
Kind::B => { /* another 30 lines */ }
}
}
// ✅ Decomposed — each function has single responsibility
fn process(input: &Input) -> Result<Output> {
match input.kind {
Kind::A => process_kind_a(input),
Kind::B => process_kind_b(input),
}
}
Test double selection — choose the right tool:
| Approach | When to Use | Crate |
|---|---|---|
| Hand-written fake | Simple trait, few methods, test needs custom stateful behavior | None (implement trait directly) |
mockall | Complex trait, need to verify call counts, argument matching, or call ordering | mockall |
| Parameterized tests | Same logic, multiple input/output pairs (like Go table-driven tests) | rstest |
| Snapshot testing | Large outputs (JSON responses, CLI output, error messages) | insta |
// ✅ Hand-written fake — simple, debuggable, no macro magic
struct FakeTaskStorage {
tasks: HashMap<String, Task>,
}
impl TaskStorage for FakeTaskStorage {
async fn get_by_id(&self, id: &str) -> Result<Task, StorageError> {
self.tasks.get(id).cloned().ok_or(StorageError::NotFound)
}
}
// ✅ mockall — when you need interaction verification
#[cfg(test)]
mock! {
pub TaskStore {}
impl TaskStorage for TaskStore {
async fn get_by_id(&self, id: &str) -> Result<Task, StorageError>;
async fn create(&self, task: &Task) -> Result<(), StorageError>;
}
}
#[tokio::test]
async fn test_service_calls_storage_once() {
let mut mock = MockTaskStore::new();
mock.expect_create()
.times(1)
.returning(|_| Ok(()));
let service = TaskService::new(mock);
service.create_task(request).await.unwrap();
}
// ✅ rstest — parameterized test cases
use rstest::rstest;
#[rstest]
#[case("valid@email.com", true)]
#[case("no-at-sign", false)]
#[case("", false)]
fn test_email_validation(#[case] input: &str, #[case] expected: bool) {
assert_eq!(is_valid_email(input), expected);
}
// ✅ insta — snapshot testing for complex outputs
use insta::assert_json_snapshot;
#[test]
fn test_task_response_shape() {
let response = TaskResponse::from(sample_task());
assert_json_snapshot!(response);
}
Prefer hand-written fakes for core domain traits — they are easier to debug and don't couple tests to implementation details. Use
mockallonly when the trait has many methods or you genuinely need interaction verification (call counts, argument matching, call ordering). Over-mocking withmockallleads to brittle tests that break on implementation changes.
Acceptable suppressions (with mandatory // ALLOW: comment):
| Lint | When Acceptable |
|---|---|
unwrap_used | In #[cfg(test)] modules only |
expect_used | In #[cfg(test)] modules, OR with a // SAFETY: comment proving infallibility, OR in a CLI main() that owns the process exit (clear message + exit code). This reconciles with the expect_used = "warn" lint level in recommended-dependencies.md — warn permits these uses while still surfacing every other expect() for review. |
module_name_repetitions | When the repetition is intentional API design |
must_use_candidate | On internal functions where the caller pattern is known |
missing_errors_doc | Temporarily during development (must be resolved before merge) |
needless_pass_by_value | When API stability requires it (with comment explaining why) |
items_after_statements | When locality of helper functions improves readability |
cast_possible_truncation | With bounds check or range validation immediately preceding the cast |
Rule of thumb: If you're about to write #[allow(clippy::...)], stop and ask: "Am I suppressing a real design problem?" If yes, fix the design. If the lint is genuinely a false positive for this specific context, suppress with a // ALLOW: comment explaining the rationale.
cargo fmt is non-negotiable — all code must be formatted
Recommended project-level Clippy configuration:
For the standard
[lints.clippy]and[lints.rust]blocks (single-crate and workspace variants), and the version pinning policy, seereferences/recommended-dependencies.md§Workspace Lint Configuration and §Starter Cargo.toml Template. Do not duplicate those blocks here — treatrecommended-dependencies.mdas the single source of truth.
Document all public items:
pub fn, pub struct, pub enum, pub trait, and pub type MUST have a /// doc commentmissing_docs lint in library crates:# In Cargo.toml
[lints.rust]
missing_docs = "warn"
// ❌ Undocumented public item
pub fn resolve_symbols(path: &Path) -> Result<Vec<Symbol>> { ... }
// ✅ Documented
/// Resolves all exported symbols from the file at `path`.
///
/// Returns parsed symbol definitions including their span information.
///
/// # Errors
/// Returns `ParseError` if the file cannot be parsed by tree-sitter.
pub fn resolve_symbols(path: &Path) -> Result<Vec<Symbol>> { ... }