The order of lifetime arguments matter?

Hi everyone,

I've been checking out the practice.rs exercises and in one of them I discovered something strange. I found out that the order if lifetime arguments in the struct declaration matters for the successful compilation. That is struct S<'a, 'b> produces different results from struct S<'b, 'a>.

So for the exercise 6 at Lifetimes -> Advanced my solution is as follows:

struct Interface<'r, 'a> {
    manager: &'r mut Manager<'a>
}

impl<'a> Interface<'_, 'a> {
    pub fn noop(self) {
        println!("interface consumed");
    }
}

struct Manager<'a> {
    text: &'a str
}

struct List<'a> {
    manager: Manager<'a>,
}

impl<'a> List<'a> {
    pub fn get_interface(&mut self) -> Interface<'_, 'a> {
        Interface {
            manager: &mut self.manager
        }
    }
}

And if I swap the Interface struct lifetime arguments: struct Interface<'a, 'r> the code ceases to compile. So it looks like there is some hidden constraints are introduced, but I haven't been able to google anything specific about this.

Does anyone know why this order changes the compiler behavior?

Of course it matters. &'a mut T<'b> is not the same as &'b mut T<'a> (given that the two lifetimes differ). &'b mut T<'a> doesn't make sense if 'a is shorter than 'b, because the mutable reference is then dangling.

That goes without saying, but in my example the field of the Interface didn't change, it's still manager: &'r mut Manager<'a>, only the order of the lifetime args changes from 'r, 'a to 'a, 'r which to my understanding shouldn't have changed anything substantially.

Uneliding the elided lifetimes, we get:

pub fn get_interface(&'list mut self) -> Interface<'list, 'a>

So the first lifetime parameter of Interface is how long lives the borrow of List, and the second one is how long lives the borrow inside List. This necessarily requires that 'list is not longer then 'a - otherwise the referenced List would have a dangling borrow.

Now, moving to the struct declaration: you have &'r mut Manager<'a>, and by the same logic, 'r is not longer then 'a.

In your original code, these two descriptions align with each other: 'list is passed in place of 'r, 'a - in place of 'a; everything works.
After swapping parameter order, however, you get 'list in place of 'a and 'a in place of 'r. Therefore, combining two requirements above, we essentially force 'r and 'a (or, alternatively, 'list and 'a) to be exactly the same, i.e. once you call get_interface, List (and Interface with it) is locked forever.

3 Likes

I feel silly for missing this, thank you! :sweat_smile:

No need to feel silly, borrowck is something that I believe can only really be internalized through a lot of exposure. You'll get there :slight_smile:

1 Like