[Pre-RFC] Lifetime inference for non-public items
Summary
Allow the compiler to infer lifetime parameters for all items (functions, structs, enums, type aliases, impl blocks) that are not pub at the crate boundary. Public API continues to require explicit annotations as today.
The problem
Private code pays the same lifetime annotation tax as public API, for zero benefit to crate consumers.
Functions โ the classic case where elision is insufficient:
// Today: must annotate manually, even though it's private
fn find_best<'a>(primary: &'a Data, fallback: &'a Data) -> &'a Summary {
if primary.is_valid() { &primary.summary } else { &fallback.summary }
}
Structs โ this is where the pain really compounds:
// Private struct โ 'a is obvious from the fields
struct ParseState<'a> {
input: &'a str,
tokens: &'a [Token],
current: &'a Token,
}
// Now EVERY function must propagate 'a:
fn parse_expr<'a>(state: &mut ParseState<'a>) -> Expr<'a> { ... }
fn parse_binary<'a>(state: &mut ParseState<'a>) -> Expr<'a> { ... }
fn parse_atom<'a>(state: &mut ParseState<'a>) -> Expr<'a> { ... }
// ... repeat 20 more times
Change one lifetime in ParseState โ update 20+ function signatures. The compiler knows the right answer. Why am I doing this by hand?
Enums, type aliases, impl blocks โ same story:
enum CacheEntry<'a> { Hit(&'a Response), Miss }
type Pair<'a> = (&'a str, &'a str);
impl<'a> ParseState<'a> { fn advance(&mut self) -> &'a Token { ... } }
The proposal
For items not reachable from outside the crate, the compiler infers lifetimes:
// All private โ no lifetime annotations needed:
struct ParseState {
input: &str,
tokens: &[Token],
current: &Token,
}
impl ParseState {
fn advance(&mut self) -> &Token { ... }
}
fn parse_expr(state: &mut ParseState) -> Expr { ... }
enum CacheEntry { Hit(&Response), Miss }
For pub items at crate boundary โ everything stays as today. When you change a private item to pub, the compiler tells you to add annotations and suggests the correct ones.
Why this makes sense
-
Closures already do this.
|x: &str, y: &str| -> &str { x }infers lifetimes from the body. Private named items are equally invisible to crate consumers. -
Precedent in Rust. RFC 2093 added inference of
T: 'abounds on structs. Variance inference is a whole-crate fixed-point analysis that works silently. This is the same philosophy. -
Precedent in other languages. Kotlin and Swift require explicit types on public API but infer for private code. The visibility-based boundary works.
-
The crate boundary is already special. Type privacy (RFC 2145),
impl Traitopacity, auto-trait leakage, orphan rules โ all use the same boundary.
Visibility rule
| Visibility | Inference? |
|---|---|
private, pub(self), pub(super), pub(crate) |
|
pub at crate boundary |
|
pub trait method declarations |
Open questions
- Struct inference strategy: infer lifetimes purely from field types (simple, local) or also from usage sites (precise, whole-crate)? Variance inference already does whole-crate analysis, so there's precedent.
- Should
pub(crate)be included? I think yes โ it's still internal to the crate. But open to discussion. - Private traits: inferring method lifetimes from implementations is the most complex part. Worth deferring to a follow-up?
- Phased approach: start with functions only? Or include structs/enums from the start? The struct case is where the biggest ergonomic win is, so I'd argue for including it.
What I'm looking for
Feedback on:
- Is this worth pursuing as an RFC?
- What scope makes sense for a first proposal (functions only vs. all items)?
- Any technical blockers I'm missing?
- Pointers to prior discussions on this topic
I have a full RFC draft ready if there's interest.