"borrowed value does not live long enough" with a generic function that returns impl trait
Asked Answered
S

2

7

I get an unexpected error from this Rust code:

struct Container<'a> {
    x: &'a i32,
}

trait Reply {}
impl Reply for i32 {}

fn json<T>(_val: &T) -> impl Reply {
    3
}

fn f() -> impl Reply {
    let i = 123;
    let a = Container { x: &i };
    json(&a)
}

Playground

The error is:

error[E0597]: `i` does not live long enough
  --> src/lib.rs:14:28
   |
12 | fn f() -> impl Reply {
   |           ---------- opaque type requires that `i` is borrowed for `'static`
13 |     let i = 123;
14 |     let a = Container { x: &i };
   |                            ^^ borrowed value does not live long enough
15 |     json(&a)
16 | }
   | - `i` dropped here while still borrowed

Why?

If I change the declaration of json() to either of these versions, the code compiles:

fn json(val: &Container) -> impl Reply
fn json<T>(val: &T) -> i32

It is only when there is both a type parameter and a returned trait object that the compiler rejects the code.

This is a reduction from a real issue we had with warp::reply::json(), but I would prefer to understand it in general.

Skillless answered 17/9, 2019 at 14:5 Comment(3)
I've looked at the first 20 hits for "borrowed value does not live long enough" on Stack Overflow but those didn't help. I didn't check the rest, sorry.Skillless
It looks like your question might be answered by the answers of Impl trait with generic associated type in return position causes lifetime error, especially this comment. If not, please edit your question to explain the differences. Otherwise, we can mark this question as already answered.Hylan
Thanks! It's certainly more relevant than what I have seen before. There are a bunch of differences, but it could be that both cases rest on the same underlying issue. I'm reading through the references in the answers.Skillless
B
1

When the arguments and the return type of a function are generic, the Rust compiler assumes that the return type could, potentially, borrow the arguments. That's why it assumes that f() returns a value referencing the local variable i.

I'm not entirely sure, but I think this is desired, because someone could implement Reply for a type where this would be problematic.

EDIT: This doesn't work because of a bug. It has already been reported on GitHub.

Berthold answered 17/9, 2019 at 16:45 Comment(7)
the Rust compiler assumes that the return type could, potentially, borrow the arguments — please enhance your answer to explain why -> impl Reply + 'static doesn't work.Hylan
I tried implementing a function that returns such a Reply but failed! Haha! Yeah, that doesn't prove anything. Is this behavior documented somewhere?Skillless
One more thing though that I forgot to mention in the question. This only fails to compile when Container contains a borrow. If it contains a plain i32, it works. Why does the compile not worry that Reply could hold a reference to a? Why only worry about i? Thanks a lot!Skillless
@DanielDarabos that's interesting. However, it only fails if the borrow references a local variable: let a = Container { x: &123 } works.Berthold
@Hylan I don't know, maybe someone who knows more about how the compiler works can answer this. Maybe this is indeed a bug.Berthold
@Berthold that's covered by Why can I return a reference to a local literal but not a variable?Hylan
There's already an issue filed on GitHub. It was linked in the comments on the original question here.Hylan
S
1

Four years later, we have an in-depth answer: Analysis of Rust issue #42940

I think the key part is this:

The value returned by test does not reference any data owned by the current function. The error message is just wrong about that.

However, the type returned by test does reference a lifetime that's only valid within the current function. That's the real problem here.

Skillless answered 24/9, 2023 at 19:58 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.