Why does the compiler think this value doesn't live long enough?

I am truly confused by this minimal example. For the life(time) of me, I cannot figure out why this value does not live long enough.

The actual concrete return type of format_with_config() should be String in all of these minimal examples, however I'm trying to get it to work with impl Debug.

I even tried to be as explicit as possible that the return value is not tied to the lifetime of &self by declaring impl Debug + 'static.

The example works if you replace all of the impl Debug + 'static with String, so I know that the intent here is valid and logically sound, but I really would like to try to get this working with impl <trait>.

The function returns an owned value, so I don't understand why the compiler thinks the borrow is invalid. What else do I have to do here to make this work?

Thank you for any insights you might be able to share!

use core::fmt::Debug;

#[derive(Debug, Default)]
struct Config {}

struct Configurable<'config, T> {
    item: T,
    config: &'config Config,

impl<'config, T> Configurable<'config, T> {
    fn share_config<R>(&self, other: R) -> Configurable<'config, R> {
        Configurable {
            item: other,
            config: self.config,

trait FormatWithConfig {
    fn format_with_config<T: Debug>(&self, other: T) -> impl Debug + 'static;

#[derive(Debug, Clone)]
struct Value {}

impl<'config> FormatWithConfig for Configurable<'config, &Value> {
    fn format_with_config<T: Debug>(&self, other: T) -> impl Debug + 'static {
        format!("{:?}", other)

struct ValueWithCount<'value> {
    value: &'value Value,
    count: usize,

impl<'value, 'config> FormatWithConfig for Configurable<'config, ValueWithCount<'value>> {
    fn format_with_config<T: Debug>(&self, other: T) -> impl Debug + 'static {
        let configured_value = self.share_config(self.item.value);

fn main() {}



   Compiling playground v0.0.1 (/playground)
error[E0597]: `configured_value` does not live long enough
  --> src/main.rs:42:9
40 |     fn format_with_config<T: Debug>(&self, other: T) -> impl Debug + 'static {
   |                                     - let's call the lifetime of this reference `'1`
41 |         let configured_value = self.share_config(self.item.value);
   |             ---------------- binding `configured_value` declared here
42 |         configured_value.format_with_config(other)
   |         ^^^^^^^^^^^^^^^^--------------------------
   |         |
   |         borrowed value does not live long enough
   |         argument requires that `configured_value` is borrowed for `'1`
43 |     }
   |     - `configured_value` dropped here while still borrowed

For more information about this error, try `rustc --explain E0597`.
error: could not compile `playground` (bin "playground") due to previous error

It compiles if you inline the variable configured_value:

impl<'config, 'value> FormatWithConfig for Configurable<'config, ValueWithCount<'value>> {
    fn format_with_config<T: Debug>(&self, other: T) -> impl Debug + 'static {

That is definitely a weird situation and I hope that someone else can explain it. It smells like one of those strange corner cases of the borrow checker that are very underdocumented.

Thanks for the reply here!

Though I think you may have returned the unit type by adding a semicolon to your one-liner, which is compatible with impl Debug + 'static.

I suspect you'll get the same error if you remove the semicolon.

Yes it is exactly that. Very subtle. I have just reproduced the same.

Sorry, that was a mistake on my part, and doesn't help you. Let me get back to you.

Seems like a bug related to trait methods returning impl trait to me. If you remove the FormatWithConfig trait from the equation (or just from the Configurable impl), it compiles just fine. I found return_position_impl_trait_in_trait can not express static lifetime bound · Issue #117210 · rust-lang/rust · GitHub where a reply says that it happens due to impl-trait return type is bounded by all input type parameters, even when unnecessary · Issue #42940 · rust-lang/rust · GitHub


Ah, this does look relevant to the issue! Thank you for finding this.

I wonder if using a trait is just off the table because of this, or if there's a way to make it work.

This is amongst known limitations of -> impl Trait + 'a return types as far as I know. I’m not sure what exactly the latest situation was for non-trait ones – it doesn’t help that the rules what parameters (lifetimes and type parameters) become part of the opaque return type also differs between the two situations (which is also a deliberate design decision, in order to make async fn in traits be equivalent to -> impl Future returning functions).

Anyways, the situation as far as I’m aware is that a type like dyn Trait + 'static can be a bit schizophrenic in that it requires for the actual type you provide in the function body to be implementing 'static, but the overall type becomes something of an Opaque<Params…> type which captures type parameters (and possibly even lifetime parameters) from the function body – in case of impl Trait return types in trait methods, that’s all lifetime parameters, in case of outside of traits only the ones you mention in the type.

So fn format_with_config<T: Debug>(&self, other: T) -> impl Debug + 'static signatures come with some type definition Opaque<'a, T> and then have the signature fn format_with_config<'a, T: Debug>(&'a self, other: T) -> Opaque<'a, T>, with the effect that if 'a is not 'static or T is not …: 'static, then the opaque types isn’t …: 'static either.

I do remember having seen some work on improving this situation, I’m not sure off the top of my head what improvements to the original situation are already in place.

Edit: Looks like Consider alias bounds when computing liveness in NLL (but this time sound hopefully) by compiler-errors · Pull Request #116733 · rust-lang/rust · GitHub is the degree to which the issue has been addressed. (Now we just gotta understand what cases this PR actually does and doesn’t fix.)


These have all been very helpful explanations and insights, everyone.

Thank you for all of the support.

It's overcapturing in RPIT: 3498-lifetime-capture-rules-2024 - The Rust RFC Book

One solution as given from the link is via TAIT: Rust Playground


Jon Gjengset gave recently a lecture at the University of Copenhagen where he also talks about the current state of impl Trait - APIT ATPIT, RPIT, RPITIT... and so on. The current and the future capturing behaviour is also mentioned. I can highly recommend watching the recording: https://www.youtube.com/watch?v=CWiz_RtA1Hw

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.