Cannot return value referencing temporary value while lifetime should be valid

Hi everyone,
I have a case I am not sure why it is not valid, here is a small recreation of the issue in playground

here is the code of the playground

trait Foo {
    fn foo(&self) -> &'_ str;
}

struct MyType<'a>(&'a str);

impl<'a> Foo for MyType<'a> {
    fn foo(&self) -> &'a str {
        self.0
    }
}

fn main() {
    let s: Vec<_> = "hello world"
        .to_string()
        .split_whitespace()
        .map(String::from)
        .collect();

    let new: Vec<&str> = s.iter().map(|s| MyType(s.as_str()).foo()).collect();
}


error[E0515]: cannot return value referencing temporary value
  --> src/main.rs:20:43
   |
20 |     let new: Vec<&str> = s.iter().map(|s| MyType(s.as_str()).foo()).collect();
   |                                           ------------------^^^^^^
   |                                           |
   |                                           returns a value referencing data owned by the current function
   |                                           temporary value created here

the returned value is my s.as_str() reference that should have a valid lifetime since the owned data is n s

as_str should create a reference to the string that is vec s
MyType::foo returns an str with the lifetime of the provided str

for example if we change line 20 in the playground to

let new: Vec<_> = s.iter().map(|s| s.as_str()).collect();

it works fine, I guess it has something to do with the trait lifetime '_(?)

the error is due to the Foo::foo() method, which borrows self, which is a temporary value constructed in the closure. if you want .foo() to return a &'a str where 'a is the lifetime in MyType<'a>, the trait definition must be modified to add a lifetime:

trait Foo<'a> {
    fn foo(&self) -> &'a str;
}

impl<'a> Foo<'a> for MyType<'a> {
    fn foo(&self) -> &'a str {
        self.0
    }
}

I think there's still something confusing going on here, so I made this post to explain it.

There's also an alternative fix at the end of this post, if you'd prefer to skip ahead.


Note how the trait definition and your implementation differ:

trait Foo {                   fn foo<'s>(&'s self) -> &'s str;           }
impl<'a> Foo for MyType<'a> { fn foo<'s>(&'s self) -> &'a str { self.0 } }
//                                       --------      ^^

In the trait definition, the returned &'s str is only valid for as long as &'s self is valid, and uses of the returned value keep *self borrowed. That's what the matching lifetimes mean. And that is how all compiling implementations of the trait act when used elsewhere, which is why your OP had an error.

But in your implementation, you return &'a str (the ^^ underlined part). The typical meaning of this method signature is that the return value may be valid longer than the &'s self, and uses of the returned value do not keep *self borrowed. But because all compiling implementations act like the trait definition, that's not what happens when you call the method in main.

@nerditation's suggestion changes the trait definition to look like your implementation, which fixes the error in the OP.


Even knowing about what I just wrote, there's still something unintuitive going on here: If the signatures mean different things in the OP, why is the implementation allowed to compile at all?

First note that the &'a str you return in the implementation can't be valid for less than the &'s self. The reason is the --- underlined part: there is a &'s MyType<'a> in the signature, which means the method has an implied 'a: 's bound.

Because of this, the implementation is more general than the trait definition. It satisfies everything that the trait needs to. And such implementations are allowed so that[1] you can do things like this:[2]

// If you elide all the lifetimes, it corresponds to:
impl<'a> Add<&'a str> for Whatever {
    type Output = Whatever;
    // Takes `'r` (unrelated to `'a`), but the trait says it takes `&'a str`!
    fn add<'r>(self, rhs: &'r str) -> Self::Output {
        Whatever
    }
}

Instead of

// The lifetimes are the same in this version, matching the trait definition
impl<'a> Add<&'a str> for Whatever {
    type Output = Whatever;
    fn add(self, rhs: &'a str) -> Self::Output {
        Whatever
    }
}

(The first one is more common because it's what you get when you elide all lifetimes.)

It allows the programmer to be a bit sloppier and think about lifetimes less (so arguably this is a complication added to the language in an attempt to be simpler).

Perhaps the OP, where you used 'a instead of eliding things, should fire a warning lint that lets you know the more general implementation can't be taken advantage of.


There is a concept of refinement which allows consumers of traits that use -> impl Trait to take advantage of some aspects of specific implementations which are more general than the trait, when the implementer has opted into allowing that. For example:

trait Example {
    fn f(&self) -> impl Sized;
    fn g(&self) -> impl Sized;
}
impl Example for () {
    // Reveal the exact return type to consumers
    #[allow(refining_impl_trait)]
    fn f(&self) -> i32 { 0 }
    // Promise that the return type is always `Send` to consumers
    #[allow(refining_impl_trait)]
    fn g(&self) -> impl Sized + Send {}
}

fn these_compile_by_taking_advantage_of_refinement() {
    let _: i32 = ().f();
    let _: &dyn Send = &().g();
}

fn these_fail<T: Example>(t: T) {
    let _: i32 = t.f();
    let _: &dyn Send = &t.g();
}

Personally I think it would be great if we got refinement for lifetimes, so that you could have fixed the issue by adding #[allow(refining_impl_trait)] to your implementation. But so far, we cannot; that kind of refinement is not implemented in the compiler.

However, you can accomplish something similar by shadowing the trait method with an inherent method (which is preferred by method dispatch).

// New
impl<'a> MyType<'a> {
    fn foo(&self) -> &'a str {
        self.0
    }
}
// You can keep this one.  I've made the signature look how it will act.
impl<'a> Foo for MyType<'a> {
    fn foo(&self) -> &str {
        self.0
    }
}

That also fixes the OP.


  1. I presume this is why anyway ↩︎

  2. You can see that 'a and 'r are independent by adding let rhs: &'a str = rhs; to the body, resulting in an error. ↩︎

Thanks for the replies, unfortunately I cannot edit the trait since in my real usecase it is a library trait (I probably should have mentioned that)
specifically ratatui ToLine

uninteresting background about my use case

I wanted to have different string formats, so I created a wrapper class that takes &str and returns a Line formatted

#[repr(transparent)]
struct FormatAsPath<'a>(&'a str);

impl<'a> ToLine for FormatAsPath<'a> {
    fn to_line(&self) -> Line<'a> {
        // style the line
   }
}

The documentation for that trait says

This trait is automatically implemented for any type that implements the Display trait. As such, ToLine shouldn’t be implemented directly: Display should be implemented instead, and you get the ToLine implementation for free.

You should implement Display, which has no borrowing problems.

you can make your wrapper type wrap the unsized str instead of the reference &str. this trick works because in the closure, you will construct a &'a MyType instead of a tempoary MyType. you do need an unsafe operation though.

#[repr(transparent)]
struct MyType(str);

impl MyType {
    fn wrap(s: &str) -> &MyType {
        // SAFETY: MyType is `repr(transparent)`
        unsafe { std::mem::transmute(s) }
    }
}

impl Foo for MyType {
    fn foo(&self) -> &str {
        &self.0
    }
}

fn main() {
    let s: Vec<_> = "hello world"
        .to_string()
        .split_whitespace()
        .map(String::from)
        .collect();

    let new: Vec<&str> = s.iter().map(|s| MyType::wrap(s.as_str()).foo()).collect();
}

the Display impl uses the default styling, I think they want to manually implement ToLine to set custom formats.

also, I guess they might want to avoid allocating a String, otherwise, they wouldn't have the borrow problem to begin with, since you can always return an owned Line in the ToLine implementation.

you could do this with the bytemuck library, as long as you don't want other fields

use bytemuck::TransparentWrapper;

#[derive(TransparentWrapper)]
#[repr(transparent)]
#[transparent(str)]
struct FormatAsPath(str);

impl ToLine for FormatAsPath {
    fn to_line(&self) -> Line<'_> {
        // style the line
   }
}

(example on the playground)

Neat! And will be more performant than what I was going to suggest: use a bumpalo Bump instance to own the 'temporary' values. I'm still posting this for cases where repr(transparent) isn't possible (i.e. MyType needs to hold other data, too):

let b = Bump::new();
let new: Vec<&str> = s.iter().map(|s| b.alloc(MyType(s.as_str())).foo()).collect();

Another approach in this case, since the type that foo is called on is fixed and hence the call does not actually have to go through a trait, is to implement foo outside the trait and then only proxy to it from the trait as well (if the trait is necessary in other places) (I'm using the name foo for both method implementations here just for fun):

impl<'a> MyType<'a> {
    fn foo(&self) -> &'a str {
        self.0
    }
}

impl<'a> Foo for MyType<'a> {
    fn foo(&self) -> &str {
        MyType::foo(self)
    }
}

...

    let new: Vec<&str> = s.iter().map(|s| MyType(s.as_str()).foo()).collect();
    dbg!(new);
    // Forcing to go via the trait would again trigger the issue:
    // let new2: Vec<&str> = s.iter().map(|s| {
    //     let m = MyType(s.as_str());
    //     <MyType as Foo>::foo(&m)
    // }).collect();