Reputation: 23
Small example to illustrate the problem. The following compiles:
fn main() {
let value: u32 = 15;
let mut ref_to_value: &u32 = &0;
fill_object(&mut ref_to_value, &value);
println!("referring to value {}", ref_to_value);
}
fn fill_object<'a>(m: &mut &'a u32, v: &'a u32) {
*m = v;
}
Now, the following gives a compile time error about mutable borrow followed by immutable borrow:
fn fill_object<'a>(m: &'a mut &'a u32, v: &'a u32) {
*m = v;
}
fill_object(&mut ref_to_value, &value);
| ----------------- mutable borrow occurs here
5 | println!("referring to value {}", ref_to_value);
| ^^^^^^^^^^^^
| |
| immutable borrow occurs here
| mutable borrow later used here
Why? I'm presuming that because I have now specified a lifetime of 'a for the reference to ref_to_value, that mutable reference is now over the entire scope (ie. main). Whereas before, without the 'a lifetime reference, the mutable reference was limited?
I'm looking for clarity on how to think about this.
Upvotes: 2
Views: 106
Reputation: 70257
Your intuition is spot on. With one lifetime,
fn fill_object<'a>(m: &'a mut &'a u32, v: &'a u32) {
*m = v;
}
All three references are required to live for the same length, so if v
lives a long time then the mutable reference must as well. This is not intuitive behavior, so it's generally a bad idea to tie together lifetimes like this. If you don't specify any lifetimes, Rust gives each reference a different one implicitly. So the following are equivalent.
fn fill_object(m: &mut &u32, v: &u32)
fn fill_object<'a, 'b, 'c>(m: &'a mut &'b u32, v: &'c u32)
(Note: The inferred lifetime of returned values is a bit more complicated but isn't in play here)
So, your partially-specified lifetimes are equivalent to
fn fill_object<'a>(m: &mut &'a u32, v: &'a u32);
fn fill_object<'a, 'b>(m: &'b mut &'a u32, v: &'a u32);
As a side note, &mut &u32
is a weird type for multiple reasons. Putting aside the fact that u32
is copy (and hence, outside of generics, useless to take immutable references of), a mutable reference to an immutable reference is just confusing. I'm not sure what you're real use case is. If this was just a test example, then sure. But if this is your real program, I recommend considering if you can get off with fill_object(&mut u32, u32)
, and if you really need the nested reference, you might consider making it a bit easier to swallow with a structure.
struct MyIntegerCell<'a>(&'a u32);
Plus some documentation as to why this is necessary. And then you would have fill_object(&mut MyIntegerCell, u32)
or something like that.
Upvotes: 2