1

I'm not able to borrow where I thought I could. I reduced the problem down to this case:

struct A<'a> {
    borrow: &'a mut u8,
}

fn does_nothing<'b, 'c>(a: &'b mut A<'c>) {
    a.borrow = a.borrow;
}
error[E0623]: lifetime mismatch
 --> src/lib.rs:6:16
  |
5 | fn does_nothing<'b, 'c>(a: &'b mut A<'c>) {
  |                            -------------
  |                            |
  |                            these two types are declared with different lifetimes...
6 |     a.borrow = a.borrow;
  |                ^^^^^^^^ ...but data from `a` flows into `a` here

It seems that a.borrow has the intersection of 'b and 'c and therefore cannot be guaranteed to still have the lifetime 'c.

I don't have any real problem with this and can work around it by making both lifetimes the same, but why this does not borrow check?


It seems structs are unimportant to showing this issue and double borrows show it easily.

I have three functions which are pretty similar, but I would have trouble knowing which one compiles and which error the non-compiling ones would give.

The simple generic function:

fn only_borrow<T>(a: &mut T) {
    *a = *a;
}

results in the error:

error[E0507]: cannot move out of `*a` which is behind a mutable reference
 --> src/lib.rs:2:10
  |
2 |     *a = *a;
  |          ^^ move occurs because `*a` has type `T`, which does not implement the `Copy` trait

Including an extra level of indirection changes the error

fn only_borrow_double<T>(a: &mut &mut T) {
    *a = *a;
}
error[E0623]: lifetime mismatch
 --> src/lib.rs:2:10
  |
1 | fn only_borrow_double<T>(a: &mut &mut T) {
  |                             -----------
  |                             |
  |                             these two types are declared with different lifetimes...
2 |     *a = *a;
  |          ^^ ...but data from `a` flows into `a` here

Changing away from the implied lifetimes can fix the error:

fn working_double<'b, T>(a: &'b mut &'b mut T) {
    *a = *a;
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
TheThirdOne
  • 427
  • 3
  • 10
  • I am not sure it answers my question. The best reasoning I can see in the answers is that you need to re borrow the slice. That doesn't explain why the reborrow is limited to the outer borrow's lifetime. I would like a little bit of a deeper explanation. – TheThirdOne Aug 18 '20 at 01:28

1 Answers1

0

You'll have to take a look at your lifetimes 'b and 'c:

  • &'b mut ... means that you have a reference that is live and valid for a "time" 'b.
  • A<'c> means that you have an object that is live and valid for 'c.

What you don't have is a specific relation between these two lifetimes. The only thing the compiler can deduce is that since A<'c> is behind a &'b, 'c must outlive 'b, i.e., whenever 'b is valid, so is 'c. Though, crucially, not the other way around.

As you show, the compiler requires 'b and 'c to be the same lifetime. Why is this?

Let us have a look at our possibilities:

  1. 'c and 'b have no relation: It is easy to see that without any relation, the compiler cannot guarantee anything about what is put in A.borrow and as such won't allow it.
  2. 'c strictly outlives 'b, i.e., some places 'c is valid 'b is not:
    a.borrow = a.borrow becomes a reborrow of a using the 'b lifetime. This answer explains why that happens. However, this means that a is now dependent on the 'b lifetime, which is not valid for some of the time a is valid (since a has the lifetime 'c). This gives the error.
  3. 'b strictly outlives 'c: If we had this relation, it might work. The reborrow will be valid, since we get a "bigger" lifetime ('b) than we asked for ('c). However, we already have 'c: 'b inferred by the compiler behind the scenes. Therefore, adding this lifetime means the two lifetimes become equal and we are then back where we started:
struct A<'a> {
    borrow: &'a mut u8,
}

/// We add the relation 'b: 'c
fn does_nothing<'b: 'c, 'c>(a: &'b mut A<'c>) {
    a.borrow = a.borrow;
}
Emoun
  • 2,297
  • 1
  • 13
  • 20
  • Your explanation about `'b : 'c` is opposite of https://doc.rust-lang.org/reference/trait-bounds.html – TheThirdOne Aug 18 '20 at 14:10
  • @TheThirdOne You are right, its the exact other way around. I can't find an intuitive way to remember the order. Good catch. I have corrected the answer. – Emoun Aug 18 '20 at 14:22
  • This answer is still misleading. The reason the "fixed" `does_nothing` works is because the compiler *does* assume that `'c: 'b` and so when you add `'b: 'c` it concludes that `'b = 'c`. So this is just the same as `does_nothing<'b>(a: &'b mut A<'b>)`. – trent Aug 18 '20 at 14:43
  • @trentcl why would the compiler assume `'c: 'b`? Do you maybe have a source explaining this? – Emoun Aug 18 '20 at 14:51
  • @trentcl It seems you are correct. [See this code that tests it](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=c36eaa7839daeb796376821180fb99e2). I'll correct the answer to reflect this. I guess the compiler looks at more than the function signature to determine relations. It doesn't do something like this with traits, so I assumed it also wouldn't do it with lifetimes. – Emoun Aug 18 '20 at 14:56
  • 1
    It's part of the well-formedness requirement for `&'b mut A<'c>`; if it assumed they were completely unrelated, the signature wouldn't compile at all. The compiler doesn't have to look at more than the signature though (at least in this case). Unfortunately well-formedness isn't terribly well documented, but [here's something I wrote to show kind of how it works](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=4e132709dfb3df4a7bf27315b5d2651c). – trent Aug 18 '20 at 15:07