I am having my own portion of lifetime/borrow checker problems.
The code:
struct Ref<'a, T>(&'a mut T);
impl<'a, T> Drop for Ref<'a, T> {
fn drop(&mut self) { }
}
fn maybe_consume<'a, T>(consume: bool, value: Ref<'a, T>) -> Option<Ref<'a, T>> {
if consume { None } else { Some(value) }
}
fn maybe_consume_reset<'a, T: Default>(consume: bool, value: Ref<'a, T>) -> Ref<'a, T> {
let reb = Ref(&mut *value.0); // The borrow starts here (it should be a reborrow for a shorter lifetime right?)
let temp = maybe_consume(consume, reb); // ... here it is transferred to `temp` (`reb` is consumed by move by value)
let res = match temp {
Some(r) => { // here it is transferred to `r` (`temp` is consumed by match by value)
drop(r);
// The borrow should end here
value
}
None => { // here it should end (`temp` is consumed by match by value)
// The borrow should end here
*value.0 = Default::default();
value
}
};
// There should be nothing to drop here, `temp` was fully destructured by match
res
}
The error:
error[E0505]: cannot move out of `value` because it is borrowed
--> src/main.rs:28:13
|
21 | fn maybe_consume_reset<'a, T: Default>(consume: bool, value: Ref<'a, T>) -> Ref<'a, T> {
| ----- binding `value` declared here
22 | let reb = Ref(&mut *value.0); // The borrow starts here (it should be a reborrow for a shorter lifetime right?)
| ------------- borrow of `*value.0` occurs here
...
28 | value
| ^^^^^ move out of `value` occurs here
...
38 | }
| - borrow might be used here, when `temp` is dropped and runs the destructor for type `Option<Ref<'_, T>>`
error[E0506]: cannot assign to `*value.0` because it is borrowed
--> src/main.rs:32:13
|
22 | let reb = Ref(&mut *value.0); // The borrow starts here (it should be a reborrow for a shorter lifetime right?)
| ------------- `*value.0` is borrowed here
...
32 | *value.0 = Default::default();
| ^^^^^^^^ `*value.0` is assigned to here but it was already borrowed
...
38 | }
| - borrow might be used here, when `temp` is dropped and runs the destructor for type `Option<Ref<'_, T>>`
My main questions are:
Where are my comments incorrect?
Why the "borrow might be used here, when temp is dropped ..."? The temp should be (at least I think so) fully consumed by match. Is there a way to convince the compiler there is always nothing to drop?
It is not entirely clear to me why your first example works. Just a guess: is it because ManuallyDrop has a trivial destructor so no drop glue for temp is inserted after the match?
let res = match temp {
Some(r) => {
drop(r);
value
}
None => {
drop(temp);
*value.0 = Default::default();
value
}
};
In one branch temp is partially moved, so it cannot be dropped later (and its parts are dropped in that branch) and in the other branch it is dropped. What can possibly happen to temp after the match?
And yes, I am now mainly exploring and trying to understand how things work. I was originally working on something, encountered an error and in an attempt to resolve it I played around and tried to simplify my code to find the cause until I found something beyond my understanding.
If you really wish, I can share my code unmodified, but I am explicitly not asking for help with my original problem. While it might be convenient to tell what I want and let you do all the hard work, I would rather understand how things work and figure my problems out by myself ... now and anytime later. (Maybe I should put the topic into a different category?)
So the question remains: Why temp gets dropped at the point where it is provably either already dropped or deconstructed?
When you partially move out of something with drop glue, some drop glue must still run at the destruction point at least sometimes. It may need to drop other fields that you didn't move, for example. Like in this playground. I hadn't really thought much about it before, but partially moving out of an enum variant also needs to "lock in" which variant the partially moved value is.
Like my previous playground shows, if you give the compiler enough information, it eliminates the end-of-scope destruction spot -- like if you unconditionally move the value on all branches. In the case where you move Some(r /* <--- moved */) but not temp, that clearly isn't happening. I'd have to play around to see if this ever happens (before optimization) when partial moves are involved (i.e. "all fields were partially moved so no reason for the drop glue of the original value to run"). Or maybe this has more to do with enum variants, I'm not sure.
The issue calls it "an bug with borrow checking", but I'd probably call it more of a shortcoming of drop checking or such. The borrow checker can't change where things drop; that information is just an input to the borrow checker.