Questions about how rust understands lifetime

static mut GLOBAL_VARS : u32 = 100;
struct X (u32);
impl X {
    // lifetime parameters are all specified for later work
    fn borrow_from_self<'h>(&'h mut self) -> &'h mut u32 {
        &mut self.0
    }
    fn borrow_from_input<'i, 'j>(&'i mut self, _in: &'j mut u32) -> &'j mut u32 {
        _in
    }
    //fn borrow_from_global<'k>(&'k mut self) -> &'static mut u32
    fn borrow_from_global<'k>(&'k mut self) -> &'k mut u32 {
        unsafe { &mut GLOBAL_VARS }
    }
}

fn main() {
    let mut v1 = X(10);
    let mut v2 = 20;
    let v3 = v1.borrow_from_input(&mut v2);
    v1.borrow_from_self();
    let v4 = v1.borrow_from_self();
    let v5 = v1.borrow_from_global();
}

I have read Lifetimes - The Rustonomicon and did some tests. But I'm still a little confused about how rust knows lifetimes.

In the nomicon document, it shows situations only with one lifetime parameter. And I want to know how rust processes it when multiple lifetime parameters given.

I mainly have 3 questions:

  1. v3 do not conflict with v4. Why?
  2. v5 returns a global variables. But it conflicts with v4. Why? (It works when fn signature replaced with the one commented)
  3. I borrow_from_self'ed twice. But no conflicts. Why?

Let me expand the fn main (with my dumb mind) according to nomicon docs :

fn main() {
    'a: {
        let v1 = X(10);
        'b: {
            let v2 = 20;
            'c: {
                let v3 = X::borrow_from_input::<'?,'?>(&'? mut v1, &'? mut v2);
                {
                    X::borrow_from_self::<'c>(&'c mut v1);
                }
                'd: {
                    let v4 = X::borrow_from_self::<'d>(&'d mut v1);
                    'e: {
                        let v5 = X::borrow_from_global::<'e>(&'e mut v1);
                    }
                }
            }
        }
    }
}

Firstly, correct me if anything is wrong.

Secondly, am I put the line without "let" correctly? If yes, question 3 is answered. And is any non-"let" lines are such analyzed(every statement/expression running in an temporary scope)?

Thirdly, can anyone fill all '?' above? AFAIK from docs, '?' should be one of 'c', 'd', 'e'. But I just don't know what to fill.

Lastly, I'd much appreciate anyone could explain the details how rust understands these. :grinning:

v3 isn’t borrowing from v1, it’s borrowing from v2. v4 is borrowing from v1. No conflict.

borrow_from_global's signature says it’s returning a borrow from self because it’s using the lifetime parameter of self in the output. The fact the body of the fn is using a global is an implementation detail (and allowed due to variance/subtyping).

The first doesn’t store the return value into anything, so the borrow of self (starts and) ends at that line.

2 Likes

Thanks for your quick reply! :smiley:

Can I conclude that after such a function call, all its input parameters which have the same lifetime specifier as its return type has (including subtypes) are regarded as borrowed? not related to whatever is returned?

e.g. After let result = foo(&mut v1, &mut v2, &mut v3); , before result dies, no matter what result is:
For sig fn foo<'a, 'b, T>(a1: &'a mut T, a2: &'a mut T, b1: &'b mut T) -> &'a T : We can't borrow v1, v2.
For sig fn foo<'a, 'b, T>(a1: &'a mut T, a2: &'a mut T, b1: &'b mut T) -> &'b T : We can't borrow v3.
For sig fn foo<'a, 'b: 'd, 'c: 'd, 'd, T>(a1: &'a mut T, b1: &'b mut T, c1: &'c mut T) -> &'d T : We can't borrow v2, v3.

Yes

Yes

Yes. Note that this form has same semantics as the first one, except applied to v2, v3 instead of v1, v2. If you associate the same lifetime parameter with b1, c1 in this example, the compiler finds the intersection for you so you don't need to spell out the outlives relationships with respect to your own d lifetime parameter.

Got it! :smirk_cat:

I'm truly grateful for your guidance!