원클릭으로
rust-programming
The essential best practices for Rust development
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
The essential best practices for Rust development
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Best practices for git usage, commits, and workflow
Optimizing arithmetic circuits / constraint systems (R1CS, PLONKish, AIR) for zero-knowledge proofs — minimizing multiplication constraints, rows, witnesses, and gate degree. Use when reducing constraint/witness counts, designing or golfing R1CS/PLONK/AIR gadgets (boolean ops, adders, range checks, hashes like SHA-256/Keccak/Poseidon), doing foreign-field/non-native or CRT/RNS arithmetic, choosing lookups vs arithmetic, or using SMT (cvc5) and SageMath (Gröbner basis) to synthesize, verify, and certify constraint encodings.
Writing, proving, and debugging Lean 4 + Mathlib. Use when proving theorems, filling `sorry`s, formalizing math, fixing broken proofs, or setting up a Lean project. Covers the cached-Mathlib setup and a plan-first proving workflow.
Performance-guided Rust optimization using benchmarks, sampling profilers, and agent-readable profiling artifacts. Use when optimizing Rust throughput, latency, CPU time, allocations, code size, compile/runtime hot paths, Criterion benchmarks, hyperfine measurements, Linux perf, macOS xctrace/Instruments, samply, flamegraphs, heap profilers, cargo-bloat, cargo-llvm-lines, or cargo-asm.
Invoke Codex CLI as a sub-agent for code tasks. Useful for second opinions, parallel exploration, or offloading isolated subtasks.
Best practices for Python development
| name | rust-programming |
| description | The essential best practices for Rust development |
Use slice pattern matching instead of index access:
// Bad: panic risk
if matching_users.len() == 1 {
let user = &matching_users[0];
}
// Good: compiler-checked
match matching_users.as_slice() {
[] => handle_empty(),
[user] => use_user(user),
_ => Err(DuplicateUsers),
}
Destructure to make defaulted fields visible:
let Foo { field1, field2, field3 } = Foo::default();
let foo = Foo { field1: custom, field2, field3 };
Force compiler errors when fields are added:
impl PartialEq for Order {
fn eq(&self, other: &Self) -> bool {
let Self { id, total, created_at: _ } = self;
// Now adding a field forces you to handle it
}
}
From and TryFrom for Type ConversionsAlways use standard conversion traits when converting between types:
From<T> for infallible, lossless, unambiguous conversions.TryFrom<T> for fallible, validating, narrowing, or otherwise rejectable conversions.to_domain, from_api, into_model, or parse_foo when From/TryFrom fits.Never hide errors in From, and never encode conversion failure with Option, sentinel values, panics, or lossy defaults.
// Bad: ad hoc conversion API, easy to miss at call sites
impl ApiUser {
fn to_domain(self) -> Result<User, UserError> {
User::new(self.email)
}
}
// Bad: fallible conversion hidden behind From
impl From<ApiUser> for User {
fn from(value: ApiUser) -> Self {
User::new(value.email).unwrap()
}
}
// Good: call sites use User::try_from(api_user) or api_user.try_into()
impl TryFrom<ApiUser> for User {
type Error = UserError;
fn try_from(value: ApiUser) -> Result<Self, Self::Error> {
User::new(value.email)
}
}
// Good: infallible conversion uses From
impl From<User> for PublicUser {
fn from(value: User) -> Self {
Self { id: value.id, email: value.email }
}
}
Avoid catch-all _ patterns:
// Bad: new variants silently fall through
match state {
State::A => {},
_ => {},
}
// Good: compiler warns on new variants
match state {
State::A => {},
State::B | State::C => {},
}
// Bad: what do these mean?
process(&data, true, false);
// Good: self-documenting
process(&data, Compression::Strong, Encryption::None);
let data = {
let mut data = get_vec();
data.sort();
data
}; // data is now immutable
pub struct ValidEmail {
value: String,
_private: (), // prevents direct construction
}
impl ValidEmail {
pub fn new(s: String) -> Result<Self, Error> {
validate(&s)?;
Ok(Self { value: s, _private: () })
}
}
#[must_use]#[must_use = "Config must be applied"]
pub struct Config { ... }
Never downgrade the edition field in Cargo.toml unless explicitly requested.
When creating new projects or when edition is unspecified, prefer edition = "2024".
When multiple crates solve the same problem, prefer the standard library first. If the surrounding codebase already uses a competing crate, follow the project. Otherwise, use the defaults below.
| Task | Default | Install | Notes |
|---|---|---|---|
| Error reporting | rootcause | cargo add rootcause | Structured, inspectable error reports. |
| Serialization derives | serde | cargo add serde --features derive | Ecosystem default for typed serialization. |
| JSON | serde_json | cargo add serde_json | Prefer typed structs over serde_json::Value unless the schema is genuinely dynamic. |
| TOML | toml | cargo add toml | Use toml_edit only when preserving comments/formatting matters. |
| Compact binary serialization | postcard | cargo add postcard | Compact Serde format; add alloc/use-std only when needed. |
| Zero-copy byte/layout parsing | zerocopy + zerocopy-derive | cargo add zerocopy zerocopy-derive | Do not write custom unsafe casts for byte/layout conversion. |
| CLI framework | clap | cargo add clap --features derive | Use stdlib args only for trivial scripts. |
| HTTP client | reqwest | cargo add reqwest --no-default-features --features rustls,json | Add cookies, compression, proxy, HTTP/2, blocking, etc. only when used. |
| TLS | rustls | cargo add rustls | Avoid OpenSSL/native TLS unless platform policy requires it. |
| QUIC | quinn | cargo add quinn | Also prefer when free to choose a secure app transport. |
| Async runtime | tokio | cargo add tokio --features macros,rt-multi-thread,signal,time | Add net, io-util, sync, fs, etc. only when used. |
| Diagnostics | tracing + tracing-subscriber | cargo add tracing; cargo add tracing-subscriber --features env-filter,fmt | Structured, async-aware diagnostics with env-based filtering. |
| SQL ORM/query builder | diesel | cargo add diesel --features postgres or sqlite/mysql | Default for SQL work. Do not start new sqlx/SeaORM use unless the project already uses them. |
| SQL migrations | diesel_migrations | cargo add diesel_migrations | Use when the app owns schema migrations. |
| SQL pooling | Diesel r2d2 feature | cargo add diesel --features postgres,r2d2 | Use for long-running apps with synchronous DB connections. |
| Datetimes | jiff | cargo add jiff | Prefer timezone-aware datetime handling over ad hoc timestamps. |
| UTF-8 paths | camino | cargo add camino | Use when paths are user-visible, serialized, or displayed; keep std Path for arbitrary OS paths. |
| Temporary files | tempfile | cargo add tempfile | Avoid manual temp path construction. |
| Data parallelism | rayon | cargo add rayon | CPU-bound work only; not async I/O concurrency. |
| Regex | regex | cargo add regex | No backreferences/look-around. |
| Memory zeroing | zeroize | cargo add zeroize --features derive | Use for secret-bearing custom types. |
| Constant-time primitives | subtle | cargo add subtle | Use for crypto-sensitive equality/selection. |
| Content hashing | blake3 | cargo add blake3 | Not a password hash or MAC replacement. |
Defaults explicitly not set: web framework, retry crate, YAML crate, low-level
crypto primitive crate. Do not design custom TCP/TLS protocols when quinn
fits the transport requirements.
Always use cargo add instead of manually editing Cargo.toml:
# Good: validates crate exists, uses latest compatible version
cargo add serde --features derive
cargo add tokio --features macros,rt-multi-thread,signal,time
cargo add reqwest --no-default-features --features rustls,json
# With specific version
cargo add clap@4.0
# Dev dependency
cargo add --dev mockall
This ensures correct syntax, validates crate names, and resolves compatible versions automatically.
Merge imports sharing a common prefix into nested groups:
// Bad: redundant prefixes
use std::collections::HashMap;
use std::collections::HashSet;
use std::io::Read;
use std::io::Write;
// Good: grouped
use std::{
collections::{HashMap, HashSet},
io::{Read, Write},
};
Always run cargo fmt after making changes:
cargo fmt
dbg! for DebuggingPrefer dbg! over println! for temporary debug output:
// Bad: manual formatting, no file/line info
println!("value = {:?}", some_value);
// Good: includes file:line, expression, and value
dbg!(&some_value);
// Prints: [src/main.rs:42] &some_value = SomeType { ... }
// Works inline in expressions
let result = dbg!(compute_something());
dbg! prints to stderr (not stdout), shows the source location, and returns the value so it can be used inline.
Avoid weakly-typed parsing (e.g. serde_json::Value, Map<String, Value>). Define structs with Serialize/Deserialize derives:
// Bad: stringly-typed, no compile-time guarantees
let v: serde_json::Value = serde_json::from_str(&data)?;
let name = v["name"].as_str().unwrap();
// Good: schema enforced at parse time
#[derive(Deserialize)]
struct Config {
name: String,
count: u32,
}
let config: Config = serde_json::from_str(&data)?;
This applies to all structured formats (JSON, TOML, YAML, etc.). Typed structs catch missing/wrong fields at deserialization, not deep in business logic.
[lints.clippy]
indexing_slicing = "warn"
fallible_impl_from = "warn"
wildcard_enum_match_arm = "warn"
fn_params_excessive_bools = "warn"
must_use_candidate = "warn"
Core principle: Make implicit invariants explicit and compiler-checked.