Untangling my lifetime and borrow rules mental model
⚓ Rust 📅 2026-01-13 👤 surdeus 👁️ 1Hi all,
I thought I got a somewhat decent mental model about the borrow rules and lifetimes, but I stumbled upon this code snippet and now I am not sure anymore I actually understand.
The example:
use std::marker::PhantomData;
struct Handle<'a> {
_fake: PhantomData<&'a ()>,
}
fn make_handle<'a>(_: &'a mut [u32]) -> Handle<'a> {
Handle { _fake: PhantomData }
}
fn main() {
let mut od = [0u32; 10];
let handle_1 = make_handle(&mut od);
// let handle_2 = make_handle(&mut od); // FAIL: cannot borrow `od` as mutable again
drop(handle_1);
}
This fails because the od is mutably borrowed a second time, and that's a no-go according to the mutable aliasing rule (not allowing multiple mutable borrows). Of course, I can see - visibly - that there are 2 mutable borrows: that - per definition - would break the rule.
But what trips me up is: the Handle that is returned from the make_handle() function does not have any actual "relation" to the od, so why is this &mut od mutable borrow still "alive" (and thus conflict with the second mutable borrow)? I was inclined to think that after the first function call to make_handle, this reference would not be borrowed anymore, so we could just go ahead with borrowing od mutably a second time (i.e., isn't this also what non-lexical lifetimes are about?).
After trying out some things and searching for a good explanation (online), it seems (at least, according to my own summary of my investigation) that the lifetime 'a of the phantom reference _fake that is tied to the lifetime of the od (but is not even a reference to the od!), makes the compiler decide that the &mut od should keep on existing, even after the function call to make_handle. For me, this feels somewhat strange that merely the lifetime usage seems to "extend" the borrow of the od, while there is no "real" connection between handle_1 and od (i.e., there is no real reference in handle_1 to the od). How should I see this; what am I missing or misunderstanding here?
(I did make one observation that does make sense IMHO: if the borrow of the first &mut od would end after the function call (like I assumed), the returned Handle would not be able to live any longer than exactly after returning from the function call as its lifetime of that Handle is tied to that mutable borrow of the od. But I am not sure this is the correct and complete picture.)
In contrast, the following example does make sense in my mental model:
fn get_mut_value<'a>(array: &'a mut [u32], index: usize) -> &'a mut u32 {
&mut array[index]
}
fn main() {
let mut od: [u32;10] = [0;10];
let handle_1 = get_mut_value(&mut od, 5);
// let handle_2 = get_mut_value(&mut od, 6); // fails, no second borrow allowed
println!("{}", handle_1);
}
Here, handle_1 has an actual relationship with the od array: it is referencing inside the actual od object, so please don't hand out a second mutable borrow because handle_1 is still used later: OK, makes sense. Put differently, I always thought that the second borrow was disallowed because the returned item has an actual reference in the array, but maybe here the reason is also rather the shared lifetime of the returned item (and not really the fact that there is reference in the returned item) as in the first example?
Could anyone give it a go to explain to me what is going on?
Thanks in advance.
6 posts - 4 participants
🏷️ Rust_feed