Mehdi Akiki
Rust Failure Atlas / Runtime, memory, and library APIs

RFA-524 · Case file with fixtures · Case 496 of 694 · Compiler evidence

A Rust Impl Block Must Declare the Lifetimes It Uses

A lifetime parameter on a type declaration is not automatically in scope for a separate impl item. Declare it after impl, then instantiate the self type with the same relationship.

Reviewed
Rust
Rust 1.98.1, edition 2024
Targets
all Rust targets
Profiles
dev, release, test

Direct answer

What this Rust failure means

Why it happens
The struct declaration and separate impl item have independent generic scopes, so using a lifetime argument does not itself declare that name.
First discriminating check
Separate the impl's parameter declaration from the self type's arguments, introduce each used lifetime, and keep method-only borrows in narrower method scopes.

I declared View<'a> correctly, then wrote impl View<'a> without declaring 'a on the impl. Rust emitted E0261 because each item introduces its own generic scope.

The failing fixture shows that a lifetime name written on the struct declaration does not remain globally available to later items.

Lifetime names are scoped parameters

struct View<'a> declares 'a for the fields and bounds inside that struct item. A separate impl must introduce its own lifetime parameter with impl<'a> before using the name in View<'a>.

The two names represent corresponding parameter positions, not one lexical declaration shared across the file.

The official E0261 page shows this exact impl form.

The repaired fixture declares then applies the lifetime

The repaired fixture writes impl<'a> View<'a>. Methods inside can use 'a, and text returns the borrow stored by the view.

The runtime example creates a view over a string literal, calls the method, and verifies the result. The same impl also applies to shorter valid borrows.

The annotation describes a relationship; it does not force every instance to be static.

The two angle-bracket lists have different roles

In impl<'a> View<'a>, the first list declares parameters available to the impl. The second list supplies arguments to the self type being implemented.

For a generic type, impl<'a, T> Wrapper<'a, T> where T: ... follows the same pattern. Leaving a parameter out of the first list makes its later use undeclared; leaving it out of the type may select another form or produce an arity error.

I read the declaration and application separately.

Methods may declare shorter independent lifetimes

An impl-wide 'a describes the stored borrow. A method can introduce 'b for a temporary input: fn compare<'b>(&self, other: &'b str). The method parameter exists only for that call.

Reusing 'a unnecessarily would require other to live as long as the stored data and reject useful callers. I give lifetimes the narrowest scope that expresses their relationship.

Names may repeat in non-overlapping scopes, but shadowing can hurt clarity.

Elision can simplify receiver-based returns

A method returning a reference borrowed from &self may rely on receiver lifetime elision. A method returning the field's underlying 'a explicitly can communicate that the result follows the stored source rather than only the current receiver borrow, subject to the signature and borrow rules.

I choose explicitness when several lifetimes interact, not because every reference needs a written name.

The generics Reference provides the parameter scope rules.

Macros can drop the declaration list

A generator may correctly render Type<'a> but forget to copy 'a into impl<...>. This happens when self-type arguments and impl generics are modelled as one string.

I store them separately and compile generated borrowed, owned, and multi-lifetime types. Fixing generated output by adding 'static changes semantics and hides the generator bug.

Impl lifetimes can be constrained with where clauses

For impl<'a, 'b> View<'a> where 'a: 'b, both names must first be declared, then the where clause relates them. A bound cannot introduce an undeclared lifetime by use. I keep declarations close and choose descriptive names when the relation is not obvious.

The impl may cover every valid 'a, or a more specific self type such as View<'static>. These are different implementation domains. Writing the concrete static form can be appropriate for methods available only on permanently borrowed data, but it should not replace a generic impl accidentally.

Avoid repeating bounds without a reason

Well-formedness requirements may be implied by fields or self types, while trait and API bounds often remain explicit. I let rustc guide missing requirements, then document non-obvious semantic constraints. Copying every bound from the type into each impl can create noise and make future evolution harder.

My E0261 checklist

  • Where should the lifetime name be declared?
  • Is it in scope for this function, struct, trait, or impl item?
  • Does an impl need impl<'a> before Type<'a>?
  • Which lifetimes belong to the type and which to one method call?
  • Can receiver elision express a simpler relationship?
  • Did a macro render arguments but omit impl parameter declarations?
  • Am I tempted to use 'static instead of declaring the real borrow?
  • Does the repaired method return data from the intended owner?

The core principle is that lifetime names are scoped generic parameters. Every impl introduces the names it uses, then applies them to the type whose borrowed relationship it implements.