1

Here is the code:

#[derive(Debug)]
struct Foo {
    f: i32,
}

#[derive(Debug)]
struct Bar<'a> {
    bar1: &'a Foo,
    bar2: &'a Foo,
}

#[allow(unused_variables)]
fn make_bar<'a>(foo1: &'a Foo, foo2: &'a Foo) -> Bar<'a> {
    Bar {
        bar1: foo1,
        bar2: foo2,
    }
}

fn extract_bar2<'a>(foo: &'a Foo) -> &'a Foo {
    let foo1 = Foo { f: 22 };
    let foo2 = make_bar(&foo, &foo1).bar1;
    foo2
}

fn main() {
    let foo = Foo { f: 11 };
    let foo1 = extract_bar2(&foo);
    println!("foo1: {:?}", foo1);
}

This gives an error:

error: `foo1` does not live long enough
  --> src/main.rs:23:32
   |>
23 |>     let foo2 = make_bar(&foo, &foo1).bar1;
   |>                                ^^^^
note: reference must be valid for the lifetime 'a as defined on the block at 21:45...
  --> src/main.rs:21:46
   |>
21 |> fn extract_bar2<'a>(foo: &'a Foo) -> &'a Foo {
   |>                                              ^
note: ...but borrowed value is only valid for the block suffix following statement 0 at 22:29
  --> src/main.rs:22:30
   |>
22 |>     let foo1 = Foo { f: 22 };
   |>                              ^

The core question is: What does a lifetime parameter actually means in the context of a struct?

More specifically: What are the consequences of having the same lifetime parameter for all fields of a struct? Do their lifetimes have to be exactly the same? Do they have to overlap? If so to what extent should they overlap?

What are the (semantic and practical) differences between the following two structs?

struct Bar<'b> {
    bar1: &'b Foo,
    bar2: &'b Foo,
}
struct Bar<'a, 'b> {
    bar1: &'a Foo,
    bar2: &'b Foo,
}
qed
  • 22,298
  • 21
  • 125
  • 196
  • [One question per question, please](http://meta.stackexchange.com/q/39223/281829). Your second half is [already answered](http://stackoverflow.com/q/29861388/155423). The Rust coding style is `snake_case`, not `camelCase`. – Shepmaster Sep 14 '16 at 16:57
  • Have you looked at any of the [91 existing questions with the same error message](http://stackoverflow.com/search?q=%5Brust%5D+%22does+not+live+long+enough%22+is%3Aq)? How does this question differ from those? Please show [what effort you have already made to solve the problem](http://meta.stackoverflow.com/q/261592/155423). – Shepmaster Sep 14 '16 at 17:00
  • I am not asking how to eliminate this error, I know how to do it. A better understanding of lifetime parameters is what I am after. – qed Sep 14 '16 at 17:29
  • 1
    I'm going to mark this as a duplicate of http://stackoverflow.com/q/29861388/155423 because you indicated that the real question is the "what are consequences" and that's answered well by that other question. – Shepmaster Sep 14 '16 at 17:41
  • I hope I have made myself clear enough now. – qed Sep 14 '16 at 18:31
  • 2
    The linked question has several issues and the last part of the accepted answer is actually just wrong -- but it's hard to answer a barrage of questions. Perhaps you could reduce it to "Why can't I call `make_bar` with these 2 parameters of different lifetimes?" which is a legitimate question that comes from the fact that the borrow checker is issuing misleading error messages. – trent Sep 14 '16 at 19:15
  • 2
    I think there is a real question here about how the compiler infers a lifetime parameter of a struct (or function) when its members (or arguments) have different lifetimes in the creator (or caller). I didn't find any promising questions on that topic when searching. – trent Sep 14 '16 at 19:30
  • @trentcl please also leave comments indicating why it's wrong, downvote incorrect answers, and/or leave a correct answer on the linked question; otherwise people might be mislead when they read it (FWIW, I'm not seeing any egregious errors over there). – Shepmaster Sep 14 '16 at 21:45
  • This was [cross-posted to Reddit](https://www.reddit.com/r/rust/comments/52vfhz/same_lifetime_parameter_for_all_fields_of_a_struct/). – Shepmaster Sep 15 '16 at 12:34

2 Answers2

2

What are the (semantic and practical) differences between the following two structs?

There is a small example to demonstrate the difference:

#[derive(Debug)]
struct Foo;

#[derive(Debug)]
struct Bar1<'b> {
    foo1: &'b Foo,
    foo2: &'b Foo,
}

#[derive(Debug)]
struct Bar2<'a, 'b> {
    foo1: &'a Foo,
    foo2: &'b Foo,
}

fn main() {//'a -->
    let foo1 = Foo;
    let ref_foo1 = 
    {//'b -->
        let foo2 = Foo;
        //error: `foo2` does not live long enough
        //replace the Bar1 with Bar2 in the row below to fix error 
        let bar = Bar1{foo1:&foo1, foo2:&foo2};
        bar.foo1
    };//--> 'b
    println!("ref_foo1={:?}", ref_foo1);
}//--> 'a

The Bar1 truncates the lifetimes of its members to their intersection. So you can't get a reference to the foo1 with lifetime 'a from the Bar1 struct. You get the reference with lifetime 'b.

I should note that the error message in this case is a bit misleading

aSpex
  • 4,790
  • 14
  • 25
0

I will paste an answer from reddit that I am really happy with.

The compiler can combine lifetimes by taking their intersection. That is, if you have 'a and 'b, then there is an intersection 'c of 'a and 'b, during which both lifetimes are 'alive'. I believe it is always the case that this intersection is equal to the shortest of 'a and 'b, because of the way scoping works, but perhaps I am mistaken.

In practice, this means that, when you see a fn<'a>(x: &'a T, y: &'a U) -> &'a V, you can put in a &'static T and a &'b U, and you will get a &'b V, because the intersection of 'static and 'b is 'b.

So, why does your method cause the compiler to complain? Because to the compiler it kinda looks like this (it's not valid syntax):

fn extract_bar2<'a>(foo: &'a Foo) -> &'a Foo {
    'b: {
        let foo1 = Foo { f: 22 };
        'c: { // The next line is wrong
            let foo2: &'a Foo = make_bar<'a>(&'a foo, &'b foo1).bar1;
            'd: {
                return foo2;
            }
        }
    }
}

I've made the scopes more explicit. What happens? The compiler knows that foo2 must have type &'a Foo, because that is what the function returns. So the Bar returned by make_bar must have had lifetime 'a: we couldn't get a &'a Foo out of it otherwise. So we must have called make_bar<'a>. But one of the arguments is wrong! foo2 does not have lifetime 'a, it has lifetime 'b, which is outlived by 'a. If you take the intersection and do this:

    let foo2: &'b Foo = make_bar<'b>(&'a foo, &'b foo1).bar1;

then foo2 does not match the return type.

When you use your second definition of Bar, the code will work, because bar1 and bar2 need not have the same lifetimes in that case. So your second Bar definition is strictly more flexible, but in practice you rarely need that extra flexibility, and the extra lifetime annotations are annoying.

Credit to https://www.reddit.com/user/thiez

qed
  • 22,298
  • 21
  • 125
  • 196