Having a C/C++ background, I am trying to understand how lifetimes, struct field access,dereferencing and referencing play together. Unfortunately I cannot find a thorough explanation in the Book nor in similar posts. Please consider the following code that compiles:
struct NotCopyable {}
struct B<'a> {
b: &'a NotCopyable, // Copy-able
c: &'a mut NotCopyable, // Not Copy-able
d: Option<&'a mut NotCopyable>, // Not Copy-able
}
impl<'a> B<'a> {
fn some_f<'s>(&'s mut self) {
// 1.
let _b: &'a NotCopyable = &(*(self.b));
// 2. COMPILER REJECTS as 'lifetime may not live long enough'
// let _c: &'a mut NotCopyable = &mut (*(self.c));
// 3.
let _c: &'s mut NotCopyable = &mut (*(self.c));
// 4.
let _d: &'a mut NotCopyable = &mut *(self.d.take().unwrap());
}
}
Assignments 1 and 4 show that the lifetime of both & () and &mut () outputs depends on the input lifetime, at least when reborrowing does not come into play. Wrong assignment 2 shows that the right-end side has lifetime 's and cannot be bound to 'a. Ok.
The lifetime mechanics in the the right-hand side of assignment 3 are unclear to me. I only concluded that the following steps somehow happen (please forgive me for the notation/terminology, but I hope the meaning is clear):
self.c: &'a mut NotCopyable -> *() -> _: 's mut NotCopyable -> &mut () -> _: &'s mut NotCopyable
that is, two noticeable things occur between the input and the output of the operator *() :
self.c, namely 'a, is replaced with self lifetime, 's.'s sticks to the output temporary object, which I improperly annotated with 's mut NotCopyable.I have read about reborrowing, but I did not get which are the rules when multiple lifetimes are involved. Could you please help me explaining what formally happens under the wood? Are there specific references on this topic?
“Reborrowing” is unfortunately not well documented. However, the way I think it is useful to understand this is:
Whenever you borrow some place (regardless of whether that borrow is a “reborrow”, you may only do so for as long as you have the appropriate access to that place.
To determine how long you have access to a place, look at the references you have to follow to get to that place, from the inside out.
T to which you have a &'a T, then you may copy the &'a T and thereby obtain access with lifetime 'a. This is only a momentary access to the place containing the &'a T, so you can stop here; the lifetimes involved in reading out a copy don't matter.T to which you have a &'a mut T then, since &mut references are exclusive, you must take an exclusive borrow of the place containing the &'a mut T to ensure exclusivity is not violated; 'a is merely an upper bound on how long your borrow can last, and you must repeat this whole process for the place containing the &'a mut T.So, in your particular case of
let _c: &'s mut NotCopyable = &mut (*(self.c));
I would analyze this borrowing operation as follows:
NotCopyable located at *self.c.*self.c is a dereference of an &'a mut NotCopyable; therefore,
'a, andself.c (step 3).&'a mut NotCopyable located at self.c.self is a &'s mut B<'a>, so we are trying to exclusively borrow from a &'s mut B<'a>. Therefore,
's, andself.So at the end of this process we have concluded that the new borrow may have any lifetime that is not longer than 'a or longer than 's. We know (via well-formedness rules) that 'a: 's, otherwise the reference &'s mut B<'a> could not validly exist. Therefore, it is sufficient to say that the new borrow may have any lifetime that is not longer than 's; and so we find that we can decide that this borrow shall have lifetime 's exactly.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With