SOC 직업 분류 기준
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
직접 명령은 검토 Prompt를 거치지 않습니다. 실행하기 전에 소스를 확인하세요.
npx skills add https://github.com/tomevault-io/skills-registry --skill rust-lifetimes명령은 한 줄로 유지됩니다. 복사하기 전에 가로로 스크롤해 전체 내용을 확인하세요.
로컬 사본을 원하시나요? SkillsMP에서 현재 제공할 수 있는 파일을 다운로드하세요.
SKILL.md 표시 중
| 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.
Lifetimes are a variety of generics that tell the borrow checker how long references must remain valid. Every reference already has a lifetime; annotations only make implicit relationships explicit so the compiler can verify them. Lifetimes prevent dangling references at zero runtime cost — they are erased before codegen.
Book: "Validating References with Lifetimes" — Chapter 10 §3 (https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html)
Do annotate when:
where clause is needed to satisfy T: 'a (E0309) or T: 'static (E0310) — note
that when a struct field is &'a T, rustc edition 2021+ implies T: 'a automatically;
the explicit bound is only required when the struct does not directly hold &'a T.Do NOT annotate when the three elision rules already cover the case (see Core Idioms). Redundant annotations add noise and are a Forbidden pattern.
// ✅ Elision rule 2: single input lifetime flows to output
fn first_word(s: &str) -> &str { ... }
// ❌ Redundant — compiler infers this identically
fn first_word<'a>(s: &'a str) -> &'a str { ... }
// ✅ Tells the borrow checker: output lives as long as the shorter of x and y
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
// ❌ E0106: compiler cannot determine which input the output borrows
fn longest(x: &str, y: &str) -> &str { ... }
// ✅ Instance cannot outlive the string slice it borrows
struct Excerpt<'a> {
part: &'a str,
}
// ❌ E0106: missing lifetime specifier on reference field
struct Excerpt {
part: &str,
}
// ✅ Rule 3: output lifetime is inferred from &self; no annotation needed
impl<'a> Excerpt<'a> {
fn level(&self) -> i32 { 3 }
fn announce(&self, note: &str) -> &str { self.part }
}
// ❌ Over-annotated — matches what the compiler infers anyway
fn announce<'b>(&'b self, note: &str) -> &'b str { self.part }
// ✅ rustc 2021+ implies T: 'a from the &'a T field — explicit bound accepted but optional
struct Wrapper<'a, T> {
value: &'a T,
}
// ✅ rustc 2021+ implies T: 'static from the &'static T field — explicit bound accepted but optional
struct Forever<T> {
value: &'static T,
}
Note (Rust edition 2021+): When a struct field is
&'a T, the compiler implies theT: 'abound automatically. WritingT: 'aexplicitly is accepted but not required. The explicit form is genuinely needed only when the struct does not directly hold a&'a Tfield but still needs the invariant — for example, via an associated type (T::Output: 'a) or a complex generic constraint where inference cannot fill it in.
(Book: "Lifetime Elision" subsection of ch10-03)
The compiler applies these rules in order before requiring explicit annotations.
Each reference parameter gets its own lifetime.
fn foo(x: &i32, y: &i32) → fn foo<'a, 'b>(x: &'a i32, y: &'b i32)
If exactly one input lifetime, it flows to all output lifetimes.
fn foo<'a>(x: &'a str) -> &str → output gets 'a automatically.
If one of the inputs is &self or &mut self, its lifetime flows to all outputs.
Methods rarely need explicit return-lifetime annotations.
If the rules exhaust without resolving every output lifetime the compiler emits E0106.
'static to Silence E0597Forbidden: Adding a 'static bound or changing a return type to &'static T solely
to stop "does not live long enough" errors.
Why: 'static means the data lives for the entire program. Forcing it hides the real
problem — a reference outliving its owner — rather than fixing the ownership relationship.
Book: "The Static Lifetime" — "most of the time, an error message suggesting the
'static lifetime results from attempting to create a dangling reference or a mismatch of
the available lifetimes."
// ❌ Cargo-culted 'static silences E0597 but creates unsound contract
fn get_name<'a>(s: &'a str) -> &'static str {
s // E0597 hidden by transmuting lifetime, not by fixing the borrow
}
// ✅ Tie the output lifetime to the input
fn get_name<'a>(s: &'a str) -> &'a str {
s
}
# Detector — candidate list, not a verdict; 'static on string literals is correct
grep -rn "'static" src/ | grep -v '"' | grep -v '// @intentional-static'
Forbidden: Returning a &T that points to a value created inside the function.
Why: The local is dropped at the closing brace; the reference would dangle. The borrow checker enforces this as E0515 ("cannot return value referencing local variable"). Book: "Generic Lifetimes in Functions" — "If the reference returned does not refer to one of the parameters, it must refer to a value created within this function. However, this would be a dangling reference because the value will go out of scope at the end of the function."
// ❌ E0515: result is dropped when the function returns
fn make_str<'a>() -> &'a str {
let result = String::from("hello");
result.as_str() // dangling after drop
}
// ✅ Return owned data; caller is responsible for its lifetime
fn make_str() -> String {
String::from("hello")
}
# Detector — E0515 is a compile error, not a runtime bug; this grep flags the pattern
# of returning a borrow of a local created inside the fn (heuristic, false positives exist)
grep -rn 'let.*=.*String::from\|let.*=.*Vec::new\|let.*=.*format!' src/ \
| grep -v '#\[cfg(test'
# Then check if any such binding is borrowed in the return position in the same function.
Forbidden: Annotating a function with <'a> and &'a parameters when all three
elision rules already resolve to the same result.
Why: Redundant annotations increase cognitive load and signal that the author did not apply the elision rules. Book: "Lifetime Elision" — "After writing a lot of Rust code, the Rust team found that Rust programmers were entering the same lifetime annotations over and over."
// ❌ Redundant annotation — elision rule 2 produces the same signature
fn first_word<'a>(s: &'a str) -> &'a str {
let boundary = s.find(' ').unwrap_or(s.len());
&s[..boundary]
}
// ✅ Let elision do its job
fn first_word(s: &str) -> &str {
let boundary = s.find(' ').unwrap_or(s.len());
&s[..boundary]
}
# Detector — any fn with a lifetime annotation is a candidate; many legitimate uses exist
# (multi-param fns that genuinely need explicit annotations will also match)
# Caveat: use -E (ERE) so the pattern is unambiguous; the old BRE form with a bare ( caused
# exit 2 under ERE-mode grep.
grep -rnE "fn .*<.*'[a-z][a-z]*.*>.*&.*'[a-z]" src/ --include='*.rs' \
| grep -v struct | grep -v impl
Box<dyn Trait + 'static> Where a Scoped Borrow SufficesForbidden: Using Box<dyn Trait + 'static> (or Arc<dyn Trait + 'static>) for
trait objects that are only needed for the duration of a known scope.
Why: 'static rules out any borrowed data from being placed in the box, forcing
unnecessary cloning or Arc-wrapping of values that have a natural shorter lifetime. The
correct bound is the scope's lifetime: Box<dyn Trait + 'a>. Book: "The Static
Lifetime" — "the solution is to fix those problems, not to specify the 'static lifetime."
// ❌ Rejects &str, &[T], and any struct containing references
fn process(handler: Box<dyn Fn() + 'static>) { ... }
// ✅ Accept anything valid for the function's call duration
fn process<'a>(handler: Box<dyn Fn() + 'a>) { ... }
// or equivalently with impl Fn when boxing is not required:
fn process(handler: impl Fn()) { ... }
# Detector — flag 'static on dyn bounds; legitimate uses exist (thread::spawn, task queues)
grep -rn "dyn.*'static\|'static.*dyn" src/ | grep -v '// @static-required'
Forbidden: A struct that holds a reference to data owned by another field of the same
struct (e.g., struct S { data: String, view: &'??? str }).
Why: When the struct moves, data moves to a new address and the reference dangles.
Rust's ownership model forbids stable addresses for ordinary stack/heap values. The
correct solutions are: (a) use an owned type (String) instead of a borrow, (b) use an
index into the data, (c) use a crate like ouroboros or self_cell that wraps the
struct in Pin, or (d) separate the data and the view into two distinct structs. Book:
"In Struct Definitions" — "An instance of ImportantExcerpt can't outlive the reference
it holds in its part field."
// ❌ Cannot express — no lifetime can describe 'own data'
struct Parser {
input: String,
current: &str, // E0106: wants to borrow from `input`, but `input` also moves
}
// ✅ Store an index instead of a reference
struct Parser {
input: String,
cursor: usize,
}
# Detector — struct fields with a bare unannotated &str / &[T] (no lifetime tick)
# The pattern skips &'a-annotated fields so it does not flag correct code like
# the Book's own ImportantExcerpt<'a>. Use only to find fields missing a lifetime
# annotation (E0106: "missing lifetime specifier").
grep -rn -A10 '^struct ' src/ --include='*.rs' | grep -E ':[ \t]*&[^'"'"'][^;]*,$'
'a: 'b Outlives ConstraintsForbidden: Adding 'a: 'b ("lifetime 'a outlives 'b") or T: 'a bounds copied
from a compiler suggestion without understanding why they are needed.
Why: 'a: 'b is a lifetime subtyping constraint — it says 'a is at least as long
as 'b. It is correct and necessary when a shorter-lived reference must hold a value
borrowed from a longer-lived region. Cargo-culting it causes over-constrained APIs that
reject valid callers. E0309 is the correct trigger: it fires when a T: 'a bound is
missing from a struct definition; it is fixed by adding the exact bound the compiler
requires, not by adding every outlives relationship in sight. Book: "Generic Type
Parameters, Trait Bounds, and Lifetimes" and error index E0309.
// ❌ Over-constrained: forces 'a to outlive 'b — requires callers to provide a longer-lived first argument
fn pick<'a: 'b, 'b>(x: &'a str, y: &'b str) -> &'b str { y }
// ✅ No constraint needed — output ties to 'b only
fn pick<'a, 'b>(x: &'a str, y: &'b str) -> &'b str { y }
// ✅ Explicit T: 'a accepted; implied by &'a T field since Rust 2021 — needed only
// when the invariant can't be inferred (e.g. associated type T::Output: 'a)
struct Wrapper<'a, T: 'a> {
inner: &'a T,
}
# Detector — explicit outlives syntax; flag for review (legitimate uses exist in
# trait-object lifetimes and complex generic bounds)
grep -rn "'"'[a-z]*:.*'"'"'[a-z]" src/ | grep -v '// @outlives-required'
Chapter 10: Generic Types, Traits, and Lifetimes https://doc.rust-lang.org/book/ch10-00-generics.html
§10.3 "Validating References with Lifetimes" — why lifetimes exist; dangling
references; the borrow checker; elision rules; struct/fn/method annotation; 'static;
combined generics + lifetimes + trait bounds
https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html
Error index E0106 — "missing lifetime specifier" — return type borrows but signature does not say from which parameter https://doc.rust-lang.org/error_codes/E0106.html
Error index E0309 — "T may not live long enough" — struct containing associated
type output where impl requires T: 'a but struct definition does not declare it;
fix: add T: 'a where-clause to the struct
https://doc.rust-lang.org/error_codes/E0309.html
Error index E0310 — "T may not live long enough" — struct stores &'static T
but T is not bounded by 'static; fix: add T: 'static
https://doc.rust-lang.org/error_codes/E0310.html
Error index E0515 — "cannot return value referencing local variable" — returning a borrow of a value that will be dropped when the function returns https://doc.rust-lang.org/error_codes/E0515.html
Error index E0597 — "does not live long enough" — reference outlives the value it
points to; primary signal that lifetimes need restructuring, not a 'static patch
https://doc.rust-lang.org/error_codes/E0597.html
T: Display + 'a)Arc/Rc and when owning data beats borrowing it across scopesSource: adelabdelgawad/rust-fullstack-agents — distributed by TomeVault.