用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/tomevault-io/skills-registry --skill rust-pattern-matching命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
| Use when this capability is needed.
> Use when this capability is needed.
Review architecture and API design for the vfs-s3 project. Use when the user mentions @architect, asks to review an issue's design, discuss module boundaries, API shape, or architectural decisions for vfs-s3. Also trigger when the user wants to create an ADR (Architecture Decision Record) or evaluate a technical approach for the project. Intended for dispatch from Codex automation or Claude routines; GitHub trigger phrase: @vfs-s3-bot please prepare design doc Use when this capability is needed.
基于 SOC 职业分类
正在显示 SKILL.md
| name | rust-pattern-matching |
| description | > Use when this capability is needed. |
Invoke this skill before:
match expression over a domain enum, Option, or Result.error[E0004]: non-exhaustive patterns.unreachable!() or .unwrap() in a match arm._ => catch-all silently swallows it).match/if let pyramid that could use let else or ?.let else for early-return on a missing value// ✅ CORRECT — happy path stays at the top level; diverge on None/Err
let Some(user_id) = extract_user_id(&req) else {
return Err(AppError::Unauthenticated);
};
// user_id: UserId is in scope here
// ❌ WRONG — indented match pyramid for what is really an early-return guard
let user_id = match extract_user_id(&req) {
Some(id) => id,
None => return Err(AppError::Unauthenticated),
};
The Book (ch06-03): "Notice that it stays on the 'happy path' in the main body of the
function this way, without having significantly different control flow for two branches the
way the if let did."
if let only when the non-match arm is truly a no-op// ✅ CORRECT — the else arm does nothing
if let Some(max) = config_max {
println!("max = {max}");
}
// ❌ WRONG — using if let when both arms do real work; use match instead
if let Some(x) = opt {
use_value(x);
} else {
use_default();
}
// ✅ BETTER
match opt {
Some(x) => use_value(x),
None => use_default(),
}
The Book (ch06-03): "Choosing between match and if let depends on what you're doing…
gaining conciseness is an appropriate trade-off for losing exhaustive checking."
if inside an arm// ✅ CORRECT — guard is colocated with the pattern; compiler sees the shape
match num {
Some(x) if x % 2 == 0 => println!("{x} is even"),
Some(x) => println!("{x} is odd"),
None => (),
}
// ❌ WRONG — nested if buries the condition; harder to audit exhaustiveness
match num {
Some(x) => {
if x % 2 == 0 { println!("{x} is even"); }
else { println!("{x} is odd"); }
}
None => (),
}
Guards bind more loosely than |: 4 | 5 | 6 if y means (4 | 5 | 6) if y.
// ✅ CORRECT — fields extracted at the match site
match msg {
Message::Move { x, y } => move_to(x, y),
Message::Write(text) => log(text),
Message::Quit => shutdown(),
Message::ChangeColor(r, g, b) => set_color(r, g, b),
}
// ❌ WRONG — match dispatches, body destructures again
match msg {
Message::Move(inner) => {
let x = inner.x;
let y = inner.y;
move_to(x, y);
}
// ...
}
@ binding to test and capture simultaneously// ✅ CORRECT — id is bound and range-tested in one arm
match msg {
Message::Hello { id: id @ 3..=7 } => println!("priority id {id}"),
Message::Hello { id: 10..=12 } => println!("secondary range"),
Message::Hello { id } => println!("other id {id}"),
}
// ❌ WRONG — range arm discards the value; body can't use it
match msg {
Message::Hello { id: 3..=7 } => println!("priority id (unknown value)"),
// ...
}
The Book (ch19-03): "@ lets us test a value and save it in a variable within one pattern."
for and function parameters// ✅ CORRECT — destructure at the binding site
for (index, value) in v.iter().enumerate() { ... }
fn print_point(&(x, y): &(i32, i32)) { ... }
// ❌ WRONG — bind whole value, destructure in body
for pair in v.iter().enumerate() {
let (index, value) = pair;
// ...
}
_ => on a Domain EnumForbidden: A _ => wildcard (or a named catch-all other =>) as the only remaining arm
of a match over an application-defined enum.
Why (Book ch06-02): The compiler enforces exhaustiveness for explicitly listed arms. A
_ => arm absorbs every variant not listed above it, including variants added in the
future. Adding a new variant silently compiles; the new case falls through to _ => with
no warning and no forced review. The compiler's exhaustiveness guarantee — "Rust even knows
which pattern we forgot!" — is completely bypassed.
// ❌ FORBIDDEN — new variants fall silently through
match status {
AgentStatus::Available => handle_available(),
AgentStatus::OnBreak => handle_break(),
_ => {} // hides future variants
}
// ✅ CORRECT — adding a new variant breaks the build at every match site
match status {
AgentStatus::Available => handle_available(),
AgentStatus::OnBreak => handle_break(),
AgentStatus::Wrapup => handle_wrapup(),
AgentStatus::Offline => {}
}
Exception: _ => is acceptable when matching external types you do not control
(e.g., std::io::ErrorKind, third-party enums), or as the last arm of a match over an
integer/string literal set. Mark with // exhaustive: external type.
# Detector — catch-all arms over local enums (heuristic; review every hit)
grep -rn '_ =>' src/ | grep -v '// exhaustive: external type' | grep -v '#\[cfg(test'
match opt { Some(x) => x, None => panic!() } Instead of ? / let elseForbidden: Manually destructuring Option or Result in a match or if let where
the non-matching case panics, calls unreachable!(), or silently returns a default.
Why (Book ch09-02, ch06-03): Rust provides the ? operator and let else exactly for
this. Manual panic arms bypass the error-propagation chain, lose context, and are invisible
to callers. .unwrap() in production paths is forbidden by the project rules; this is the
structural equivalent.
// ❌ FORBIDDEN — manual panic in match arm
let conn = match pool.get() {
Ok(c) => c,
Err(e) => panic!("no connection: {e}"),
};
// ❌ FORBIDDEN — unreachable!() masking a real case
let value = match opt {
Some(v) => v,
None => unreachable!("should always be Some here"),
};
// ✅ CORRECT — propagate with ?
let conn = pool.get()?;
// ✅ CORRECT — diverge with let else
let Some(value) = opt else {
return Err(AppError::MissingValue);
};
# Detector — panic!/unreachable! inside match/if-let arms
grep -rn 'panic!\|unreachable!' src/ | grep -v '#\[cfg(test\|// @invariant'
match/if let Pyramids Instead of ? or let elseForbidden: More than one level of match or if let nesting when the outer level only
exists to unwrap an Option/Result before passing to the inner logic.
Why (Book ch06-03): Nesting escalates indentation, inverts the happy path, and makes
the control flow opaque. ? or let else flattens the pyramid and keeps the happy path
linear.
// ❌ FORBIDDEN — pyramid of doom
async fn process(req: Request) -> Result<Response, AppError> {
if let Some(session) = req.session() {
if let Ok(user) = db.find_user(session.user_id).await {
if let Some(profile) = user.profile {
return Ok(build_response(profile));
}
}
}
Err(AppError::Unauthorized)
}
// ✅ CORRECT — flat happy path
async fn process(req: Request) -> Result<Response, AppError> {
let session = req.session().ok_or(AppError::Unauthorized)?;
let user = db.find_user(session.user_id).await?;
let profile = user.profile.ok_or(AppError::Unauthorized)?;
Ok(build_response(profile))
}
# Detector — nested if let (heuristic)
# Caveat: fires on ALL if-let occurrences, not only nested ones — manually inspect each hit
# for actual nesting (a second if-let inside the body of the first).
grep -rn 'if let' src/ --include='*.rs' -A3 | grep 'if let'
unreachable!() / _ => unreachable!() Masking Real CasesForbidden: Using unreachable!() as the body (or entire else branch) of a match arm
over a domain enum in production code, outside a #[cfg(test)] block.
Why (Book ch06-02, ch19-03): unreachable!() is a runtime panic. When a future
variant is added or when an assumption is violated, the process crashes instead of failing
gracefully or producing a compile error. It also suppresses the exhaustiveness signal — the
compiler sees the arm and considers the match exhaustive; you lose the forced-review
property you'd get from a missing arm.
// ❌ FORBIDDEN
match event {
CallEvent::Started => handle_start(),
CallEvent::Ended => handle_end(),
_ => unreachable!("unexpected call event"),
}
// ✅ CORRECT — enumerate every variant; the compiler breaks the build on future additions
match event {
CallEvent::Started => handle_start(),
CallEvent::Ended => handle_end(),
CallEvent::Held => handle_hold(),
CallEvent::Resumed => handle_resume(),
}
# Detector
grep -rn 'unreachable!' src/ | grep -v '#\[cfg(test\|// @invariant'
&Some(ref x) Binding (Pre-2018 Style)Forbidden: Writing &Some(ref x), &Ok(ref e), or manually sprinkling ref inside
patterns when matching a reference.
Why (Rust Reference): Rust's default binding modes (Edition 2018+) automatically
dereference the scrutinee and bind variables by reference when matching a &T. There is no
need to write ref or & inside patterns in ordinary match expressions. The cargo-culted
form is unnecessary noise in Edition 2018+ — binding modes handle the reference
automatically, and the explicit form confuses readers familiar with modern Rust.
// ❌ FORBIDDEN — pre-2018 cargo-cult
match &some_option {
&Some(ref x) => use_ref(x),
&None => {},
}
// ✅ CORRECT — binding modes handle the ref automatically
match &some_option {
Some(x) => use_ref(x), // x: &T
None => {},
}
// ✅ ALSO CORRECT — match the owned value directly
match some_option {
Some(x) => consume(x), // x: T
None => {},
}
# Detector — explicit ref keyword inside patterns (rare legitimate uses exist; review each)
# Caveat: matches ref in matches!() macros and in owned-value patterns — expected false
# positives. Restrict to Rust source only to avoid Cargo.lock/doc noise.
grep -rn '\bref\b' src/ --include='*.rs' | grep -v '//' | grep -v '#\[cfg(test'
Result::Err via _ or Silently Swallowing ItForbidden: Matching Result with a _ => arm or Ok(_) | Err(_) => () construction
that discards the error path without logging or propagating.
Why (Book ch06-02): The exhaustiveness guarantee exists to prevent exactly this. A
Result::Err that falls into _ is a silent failure — the operation failed but the caller
receives no signal. Clippy's unused_must_use lint catches the top-level case but not a
match that explicitly handles every arm (even if the Err arm does nothing).
// ❌ FORBIDDEN — Err arm silently discarded
match db.save(record).await {
Ok(row) => process(row),
_ => {} // DB error: swallowed
}
// ❌ FORBIDDEN — if let discards the Err silently
if let Ok(row) = db.save(record).await {
process(row);
}
// (acceptable ONLY if the caller intentionally ignores failure AND that is documented)
// ✅ CORRECT — propagate
let row = db.save(record).await?;
process(row);
// ✅ CORRECT — log and continue deliberately
match db.save(record).await {
Ok(row) => process(row),
Err(err) => tracing::error!(?err, "save failed; skipping record"),
}
# Detector 1 — explicit Err(_) arm discarded (matches only the Err(_) => {} form)
grep -rn -E 'Err\(_\)\s*=>\s*\{\}|Err\(_\)\s*=>\s*\(\)' src/ --include='*.rs' | grep -v '//'
# Detector 2 — wildcard arm on a Result-producing expression (catches the _ => {} example above)
grep -rn -E '_ =>\s*\{\}|_ =>\s*\(\)' src/ --include='*.rs' -B3 | grep -E 'match.*Result|\.await'
| Construct | Accepts | Compiler reaction on wrong kind |
|---|---|---|
let x = … | Irrefutable only | E0005 — refutable pattern in local binding |
fn f(x: T) | Irrefutable only | same |
for x in … | Irrefutable only | same |
if let / while let | Both (refutable preferred) | warn(irrefutable_let_patterns) — Rust emits a lint warning, no W-code |
let … else | Refutable (irrefutable makes else dead code) | warning |
match last arm | Irrefutable (covers remainder) | E0004 if total is not exhaustive |
match non-last arms | Refutable | — |
The Book (ch19-02): "Function parameters, let statements, and for loops can only accept
irrefutable patterns, because the program cannot do anything meaningful when values don't
match."
When you match a &T value, Rust Edition 2018+ automatically adjusts the binding mode so
that variables in the pattern bind by reference. You do not need ref or & decorators
inside the pattern.
let points: Vec<Point> = vec![Point { x: 1, y: 2 }];
// x and y bind as &i32 — no explicit ref needed
for Point { x, y } in &points {
println!("{x}, {y}");
}
Only write ref when you explicitly need a reference inside a pattern where the scrutinee
is owned (rare, usually in complex nested destructuring).
All citations from The Rust Programming Language (2024 edition):
| Topic | Chapter | Canonical URL |
|---|---|---|
Enums and match introduction | Ch 6.0 | https://doc.rust-lang.org/book/ch06-00-enums.html |
match exhaustiveness, E0004, wildcards, binding | Ch 6.2 | https://doc.rust-lang.org/book/ch06-02-match.html |
if let, let else | Ch 6.3 | https://doc.rust-lang.org/book/ch06-03-if-let.html |
| All the places patterns can be used | Ch 19.1 | https://doc.rust-lang.org/book/ch19-01-all-the-places-for-patterns.html |
| Refutability | Ch 19.2 | https://doc.rust-lang.org/book/ch19-02-refutability.html |
Pattern syntax (or-patterns, ranges, @, guards, destructuring, _, ..) | Ch 19.3 | https://doc.rust-lang.org/book/ch19-03-pattern-syntax.html |
Note on chapter numbering: The 2024 edition renumbered the Patterns chapter from Ch 18 to Ch 19. The original URLs (
ch18-*) redirect to thech19-*equivalents.
Run all six detectors in a single sweep to audit a codebase for pattern-matching violations:
echo "=== F1: catch-all _ => on domain enums ==="
grep -rn '_ =>' src/ --include='*.rs' | grep -v '// exhaustive: external type' | grep -v '#\[cfg(test'
echo "=== F2: manual panic/unreachable in match arms ==="
grep -rn 'panic!\|unreachable!' src/ --include='*.rs' | grep -v '#\[cfg(test\|// @invariant'
echo "=== F3: nested if-let (heuristic — fires on ALL if-let; inspect for nesting) ==="
grep -rn 'if let' src/ --include='*.rs' -A3 | grep 'if let'
echo "=== F4: unreachable! outside test context ==="
grep -rn 'unreachable!' src/ --include='*.rs' | grep -v '#\[cfg(test\|// @invariant'
echo "=== F5: cargo-culted ref inside patterns ==="
grep -rn '\bref\b' src/ --include='*.rs' | grep -v '//' | grep -v '#\[cfg(test'
echo "=== F6a: explicit Err(_) arm silently discarded ==="
grep -rn -E 'Err\(_\)\s*=>\s*\{\}|Err\(_\)\s*=>\s*\(\)' src/ --include='*.rs' | grep -v '//'
echo "=== F6b: wildcard arm on Result-producing expression ==="
grep -rn -E '_ =>\s*\{\}|_ =>\s*\(\)' src/ --include='*.rs' -B3 | grep -E 'match.*Result|\.await'
rust-panic-vs-result — when to use ? vs explicit match vs unwrap; the full taxonomy
of panic-vs-propagate decisions.rust-traits-generics — when pattern matching interacts with impl Trait or generic
bounds (e.g., matching on a Box<dyn Error> requires downcast_ref, not patterns).rust-error-handling — AppError enum design, #[from] chains, and how exhaustive
match on AppError in IntoResponse is the correct mapping layer.Source: adelabdelgawad/rust-fullstack-agents — distributed by TomeVault.