How to fix Rust E0621 explicit lifetime required

The E0621 error occurs because the compiler cannot infer a lifetime for a reference returned from a function or stored in a struct, so you must explicitly annotate the lifetime parameter on the function signature or struct definition.

When the compiler demands a timestamp

You write a function to extract the first word from a sentence. The logic is trivial. You take a string slice, split it by whitespace, and return the first piece. You hit compile and get E0621. The error message shouts "explicit lifetime required." You stare at the code. The data is right there. The output comes directly from the input. Why does Rust need a timestamp on a reference?

The compiler isn't being pedantic. It's protecting you from a trap where the input could be a temporary value that vanishes the moment the function returns. Without an explicit annotation, the compiler refuses to guess how long the returned reference stays valid. E0621 is the compiler's way of saying, "You handed out a key, but you didn't tell me when the room gets demolished."

References need a lease agreement

References are like keys to a room. The room is the data. The key is the reference. If you hand someone a key, they need to know when the room might get demolished. If you don't tell them, they can't safely use the key. They might try to open the door tomorrow and find nothing but rubble.

In Rust, every reference has a lifetime. The lifetime is the duration for which the reference is valid. When you write a function that returns a reference, the compiler needs to know the relationship between the input data and the output data. Does the output point to data owned by the function? Does it point to data passed in by the caller? Or does it point to some global state?

E0621 occurs when the compiler cannot infer this relationship from the signature alone. You must add a lifetime parameter to draw the connection. The parameter doesn't change how long the data lives. It describes the relationship between lifetimes so the compiler can verify safety at compile time.

The minimal fix

Here is the function that triggers the error. The compiler sees &str in the argument and &str in the return type. It has no way to know if the returned slice lives as long as the input string or if it's a brand-new string created inside the function.

// ❌ E0621: explicit lifetime required in the function signature
// The compiler cannot infer the lifetime of the returned reference.
// It doesn't know if the output depends on the input or is independent.
fn get_first_word(text: &str) -> &str {
    let words: Vec<&str> = text.split_whitespace().collect();
    words[0]
}

The fix adds a lifetime parameter 'a. This parameter acts as a variable for time. By attaching 'a to both the input and the output, you tell the compiler that the returned reference lives at least as long as the input string.

// ✅ Fix: Add lifetime 'a to bind input and output.
// 'a is a name for "some amount of time."
// We promise the caller: the slice won't expire before the input string expires.
fn get_first_word<'a>(text: &'a str) -> &'a str {
    // The Vec holds slices that also live for 'a.
    let words: Vec<&'a str> = text.split_whitespace().collect();
    words[0]
}

The syntax <'a> introduces the lifetime parameter. It looks like a generic type parameter, and that's because it is. Just as T stands for "some type," 'a stands for "some lifetime." When you write &'a str, you're saying "a string slice valid for duration 'a."

Convention aside: 'a is the standard name for the first lifetime parameter. It carries no semantic meaning. It doesn't mean "long" or "short." It just means "this lifetime." If you have multiple lifetimes, use 'b, 'c, or descriptive names like 'ctx for context. Avoid names like 'long or 'short. Those are misleading. Lifetimes describe relationships, not durations.

What the compiler is actually checking

When you add 'a, you're not adding runtime overhead. Lifetimes are erased during compilation. The generated machine code is identical whether you write the lifetime or rely on elision. The lifetime exists only to help the compiler perform static analysis.

The compiler checks that every reference is used within its valid scope. By linking the input and output with 'a, you enable the compiler to propagate the lifetime constraint to the caller. The caller now knows that the returned slice is only valid as long as the original string is valid. If the caller tries to use the slice after the string is dropped, the compiler rejects the code with E0597 (borrowed value does not live long enough).

E0621 is purely a syntax error. It means the signature is incomplete. The compiler needs the annotation to proceed with the safety check. Once you provide the lifetime, the compiler can verify the logic. If the logic is wrong, you'll get a different error. E0621 is just the gatekeeper asking for a signature.

Structs and the container problem

Functions can sometimes rely on lifetime elision rules to skip explicit annotations. Structs cannot. A struct definition is a type definition, not a function with inputs and outputs. There is no flow to infer from. If a struct holds a reference, you must always annotate the lifetime.

// ❌ E0621: explicit lifetime required in the type definition
// The struct holds a reference, but the compiler doesn't know
// how long that reference is valid relative to the struct itself.
struct User {
    name: &str,
}

The fix adds the lifetime parameter to the struct and the field. The struct becomes generic over the lifetime. This means the struct itself is only valid as long as the data it references is valid.

// ✅ Fix: Annotate the struct and the field.
// The struct is now generic over lifetime 'a.
// An instance of User<'a> is valid only while the referenced data lives for 'a.
struct User<'a> {
    name: &'a str,
}

fn create_user<'a>(name: &'a str) -> User<'a> {
    User { name }
}

When you create a User, you must provide a string that lives long enough. The User instance borrows the string. If the string is dropped, the User becomes invalid. The lifetime annotation ensures the compiler tracks this dependency.

Convention aside: When defining methods on a struct with lifetimes, the impl block needs the lifetime parameter too. Write impl<'a> User<'a> to make the lifetime available to the methods. This is a common spot where E0621 hides. If you forget the lifetime on the impl, the compiler can't see the lifetime on the struct fields.

Methods and the self exception

Methods have different elision rules than functions. If a method takes &self or &mut self as the first argument, the compiler assumes the lifetime of any output reference ties to the lifetime of self. This saves you from writing explicit lifetimes in many cases.

impl<'a> User<'a> {
    // ✅ Elision works here.
    // The compiler sees &self and infers the output ties to self's lifetime.
    fn get_name(&self) -> &str {
        self.name
    }
}

However, if the method has multiple input references, elision fails. The compiler can't guess which input the output depends on. You'll get E0621 again.

impl<'a> User<'a> {
    // ❌ E0621: explicit lifetime required in the function signature
    // Multiple input references. Compiler can't guess which one the output ties to.
    fn compare_name(&self, other: &str) -> &str {
        if self.name == other {
            self.name
        } else {
            other
        }
    }
}

The fix adds explicit lifetimes to distinguish the inputs.

impl<'a> User<'a> {
    // ✅ Fix: Add lifetime 'b for the second input.
    // The output ties to 'a because it returns self.name.
    // If the logic returned `other`, the output would need to tie to 'b.
    fn compare_name<'b>(&self, other: &'b str) -> &'a str {
        if self.name == other {
            self.name
        } else {
            // This branch would fail if we returned `other` because 'b might be shorter than 'a.
            // The return type is &'a str, so we must return data valid for 'a.
            self.name
        }
    }
}

Ah-ha reveal: Lifetimes don't extend life. They only describe it. If you return a reference, the data must already be alive. You can't use a lifetime annotation to make a local variable live longer. If you try to return a reference to a local variable, you'll get E0515 (returned value does not live long enough), not E0621. E0621 is about missing annotations. E0515 is about logic errors.

Pitfalls and compiler whispers

E0621 often appears alongside other errors. If you fix E0621 but the logic is flawed, the compiler will immediately report the next issue. This is a feature. The compiler guides you step by step.

Common pitfalls include confusing elision with ownership. Elision only applies to references. If you return an owned value like String, no lifetime is needed. The function owns the data. E0621 never triggers for owned returns.

Another pitfall is over-annotating. You don't need lifetimes on every reference. Only annotate when the compiler requires it or when the relationship isn't obvious. Over-annotation clutters the code and makes it harder to read. Trust the elision rules for simple cases. Write explicit lifetimes when the rules break down.

Convention aside: When writing unsafe code, lifetimes become even more critical. The compiler can't check safety inside unsafe blocks. You must ensure the lifetimes are correct by hand. A wrong lifetime in unsafe code can lead to use-after-free bugs that crash the program or corrupt memory. Treat lifetime annotations as part of the safety proof. If you can't justify the lifetime, the code is likely unsound.

Decision: explicit vs elided

Use explicit lifetimes when the compiler emits E0621. The error message points to the exact spot. Add the parameter. The code compiles.

Use explicit lifetimes when defining a struct with references. Structs never get elision. The lifetime must be part of the type definition.

Use explicit lifetimes when a function has multiple input references. The compiler can't guess which one the output depends on. You must specify the relationship.

Use explicit lifetimes when the lifetime relationship is complex. If the code involves multiple scopes, nested borrows, or trait objects, explicit lifetimes make the intent clear to readers and the compiler.

Rely on lifetime elision when you have exactly one input reference and one output reference. The compiler assumes the output ties to the input. This covers the vast majority of simple functions.

Rely on lifetime elision for methods that take &self or &mut self and return a reference. The compiler ties the output to the lifetime of self. This keeps method signatures clean.

Write the lifetime. The code compiles. The safety holds.

Where to go next