6

I recently had an error which was simply resolved by changing

impl<'a> Foo<'a> {
    fn foo(&'a self, path: &str) -> Boo<'a> { /* */ }
}

to

impl<'a> Foo<'a> {
    fn foo(&self, path: &str) -> Boo { /* */ }
}

which did not make sense according to my understanding, as I thought that the second version is exactly the same as the first with applied lifetime elision.


In case we introduce a new lifetime for the method this seems to be the case according this example from the nomicon.

fn get_mut(&mut self) -> &mut T;                        // elided
fn get_mut<'a>(&'a mut self) -> &'a mut T;              // expanded

So what are the differences between this and my first code snipped.

lncr
  • 826
  • 8
  • 16

1 Answers1

8

Lifetime 'a in fn foo(&'a self, ...) ... is defined for impl<'a>, that is it is the same for all foo calls.

Lifetime 'a in fn get_mut<'a>(&'a mut self) ... is defined for the function. Different calls of get_mut can have different values for 'a.

Your code

impl<'a> Foo<'a> {
    fn foo(&'a self, path: &str) -> Boo<'a> { /* */ }
}

is not the expansion of elided lifetime. This code ties lifetime of borrow &'a self to the lifetime of structure Foo<'a>. If Foo<'a> is invariant over 'a, then self should remain borrowed as long as 'a.

Correct expansion of elided lifetime is

impl<'a> Foo<'a> {
    fn foo<'b>(&'b self, path: &str) -> Boo<'b> { /* */ }
}

This code doesn't depend on variance of structure Foo to be able to borrow self for shorter lifetimes.

Example of differences between variant and invariant structures.

use std::cell::Cell;

struct Variant<'a>(&'a u32);

struct Invariant<'a>(Cell<&'a u32>);

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

impl<'a> Invariant<'a> {
    fn foo(&'a self) -> &'a u32 {
        self.0.get()
    }
}

fn main() {
    let val = 0;
    let mut variant = Variant(&val);// variant: Variant<'long>
    let mut invariant = Invariant(Cell::new(&val));// invariant: Invariant<'long>
    {
        let r = variant.foo();
        // Pseudocode to explain what happens here
        // let r: &'short u32 = Variant::<'short>::foo(&'short variant);
        // Borrow of `variant` ends here, as it was borrowed for `'short` lifetime

        // Compiler can do this conversion, because `Variant<'long>` is
        // subtype of Variant<'short> and `&T` is variant over `T`
        // thus `variant` of type `Variant<'long>` can be passed into the function 
        // Variant::<'short>::foo(&'short Variant<'short>)
    }
    // variant is not borrowed here
    variant = Variant(&val);

    {
        let r = invariant.foo();
        // compiler can't shorten lifetime of `Invariant`
        // thus `invariant` is borrowed for `'long` lifetime
    }
    // Error. invariant is still borrowed here
    //invariant = Invariant(Cell::new(&val));
}

Playground link

red75prime
  • 3,733
  • 1
  • 16
  • 22
  • Second variant tells the compiler that `Boo<'b>` should live as long as borrow `&'b self`. That is when `Boo<'b>` is no longer in lexical scope where it was taken, `self` is no longer borrowed. – red75prime Aug 23 '17 at 09:48
  • First variant ties lifetime of borrow `&'a self` to the lifetime of structure `Foo<'a>`. If `Foo<'a>` is [invariant](https://doc.rust-lang.org/nomicon/subtyping.html#variance) over `'a`, it means that `self` should remain borrowed as long as `'a`. – red75prime Aug 23 '17 at 09:53
  • I added a bit of explanation why variance of `Foo` affects your code. – red75prime Aug 24 '17 at 11:34
  • great, now I understand why I failed at creating a MVCE. My original struct contained something which prevents it from being variant while my test structs only contained `&str` which does not prevent variance. – lncr Aug 24 '17 at 12:13