Help on fn with disjoint lifetime annotations

The code below doesn't compile because of the call to bar - comment out that call, and it compiles and runs. Why aren't the disjoint lifetimes 'a and 'b sufficient to make the bar call legal? Also, why don't either of the where clauses I've commented out have any effect on the error?

Suppose I wanted to write bar such that it just did the work of the two baz calls independently on each of its args. What would bar's signature have to look like to allow that?


struct Foo {
    f : i32,
}

fn bar<'a, 'b, T>(_x : &'a mut T, _y: &'b mut T) 
// Why don't either of these where clauses change things?:
// where a' : 'b
// where 'b : 'a
{
}

fn baz<T>(_x : &mut T) {}

fn main() {
    let mut j = Foo { f : 13, };
    let mut x = &mut j;
    {
        let mut i = Foo { f : 42, };
        let mut y = &mut i;
        bar(&mut x, &mut y); // why is this bar call a problem?
        baz(&mut y); // yet neither of these baz calls..
        baz(&mut x); // ..is a problem
    }
    println!("x.f = {} ", x.f);
    
}

(Playground)

1 Like

Oh - I see what is going on. The generic type T is capturing the inner reference lifetime. Either of these work:

fn bar<'a, 'b, 'c, 'd, T>(_x : &'a mut&'b mut T, _y: &'c mut&'d mut T)

fn bar<'a, 'b, T, U>(_x : &'a mut T, _y: &'b mut U)
2 Likes

Right, T must resolve to a single type, which forces the inner lifetimes to be the same.

3 Likes

In most practical cases, what you should do is avoid creating a double mutable reference at the call site, and instead reborrow the x and y references:

// implicit reborrow (even though this looks like a move, `x` is still usable later)
bar(x, y);

// explicit reborrow, if you prefer
bar(&mut *x, &mut *y);

bar(&mut x, &mut y) would also turn into reborrowing if bar weren't generic. If the type signature of bar took &mut Foos, then the compiler would automatically insert the dereferencing needed to get from an &mut &mut Foo to an &mut Foo — but since bar is generic, it doesn't know that would be appropriate.

1 Like

This was far from practical. I am trying to gain a good understanding of lifetime annotations, and was trying to tease out edge cases. What I'd really like is to understand what the constraint system that the borrow checker is solving looks like both inside and outside a function. In other words, be able to write out all of the constraints and examine them myself. I have read the chapters about lifetimes in the nomicon, but still have ambiguities in my understanding. In this case, I was teasing out how unrelated lifetimes work - and they do what I expected, after correcting what I had wrong in this test case.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.