In broad strokes, the borrow checker
- figures out where all lifetimes must be alive, due to constraints and the uses of values
- figures out where and how all places must be borrowed
- checks every use of every place to see if the use conflicts with a borrow
Looking at your attempts, there are probably some misconceptions around lexical scopes ({ ... }
). The main interaction of borrow checking with scopes is that destructors can run when values go out of scope. That can...
- mean a lifetime/borrow has to be alive at the end of a scope
- count as the use of a place
But in your playground, all the types have trivial destructors that don't cause the lifetimes/borrows to be alive, and you don't try to keep a variable borrowed while it goes out of scope either. So the nested scopes you've added have no effect on the borrow checking.
Borrow checking doesn't care which lexical scope a variable was declared in. It does care when the variable is used, including going out of scope. Lifetimes are also "forward-facing": they don't care about parts of the code that are before when the borrow actually happens.
Additionally, all your borrows are shared-borrows and most your uses are compatible with being shared-borrowed too, so there's not a lot of chance for conflicts.
OK, that was a lot of abstract talk. Let's look at something more concrete.
Here, the borrows only have to last until the println!
, because you don't try to use them after that. Making the lifetime the same and having them end after the println!
is a valid solution to the lifetime constraints. Where inner
and outer
were declared matters not at all.
let invalid = SomeStruct {
long_ref: &inner, // -------- 'long -------------+
short_ref: &outer, // -------- 'short ------------+
}; // |
println!( // |
"(invalid) {}, {}", // |
invalid.long_ref, // |
invalid.short_ref // |
); // |
// ---- no more uses of 'long or 'short --------------+
}
In the second attempt, it doesn't matter where foo
was declared, either. The "borrows can just be the same and end after the last use of foo
" part is pretty much the same. Technically the borrowing is more complicated, although it didn't matter to the example.
fn break_attempt2() {
let outer = 12;
let mut foo = SomeStruct { // 'long, 'short
long_ref: &outer, // 'outer_1: 'long
short_ref: &outer, // 'outer_2: 'short
};
println!("orig: {}, {}", foo.long_ref, foo.short_ref);
{
let inner = 34;
foo.short_ref = &inner; // 'inner_1: 'short
println!("valid mut: {}, {}", foo.long_ref, foo.short_ref);
// `inner`'s borrow can end here because the borrowing
// reference gets overwritten. Same with `outer`,
// technically, but it's immediately borrowed again
foo.short_ref = &outer; // 'outer_3: 'short
foo.long_ref = &inner; // 'inner_2: 'long
println!("invalid mut: {}, {}", foo.long_ref, foo.short_ref);
// inner can be not-borrowed again
foo.long_ref = &outer; // 'outer_4: 'long
foo.long_ref = &outer; // 'outer_5: 'long
println!("restored: {}, {}", foo.long_ref, foo.short_ref);
// No more uses of borrowed values: 'long and 'short
// can end here
}
println!("DONE! {}", outer);
}
On top of all that, the lifetimes in your SomeStruct
are covariant, meaning that they can coerced to be shorter.... ah as I write this, I see someone else has already pointed out how this can make a demonstration harder. But it is still possible to have conflicts without invariance or nesting.
Here's one demonstration of "breaking the constraint" without getting rid of the covariance, by coding an unsatisfiable ascription:
fn break_attempt() {
let mut foo: SomeStruct::<'_, 'static> = SomeStruct {
long_ref: &0,
short_ref: &0
};
let local = 0;
foo.long_ref = &local;
}
And here's another example which is probably more illustrative.
fn break_attempt() {
let mut a = 0;
let b = 0;
let foo = SomeStruct {
long_ref: &a,
short_ref: &b,
};
// Overwriting conflicts with shared borrows
a = 0;
// Use the short borrow, forcing 'short to be alive
println!("{}", foo.short_ref);
// 'long: 'short, so 'long has to be alive too
// So it had to be alive when `a` was overwritten too
// But that's a conflict
}