rust icon indicating copy to clipboard operation
rust copied to clipboard

Wrapper variable considered live after all of its constituents are consumed

Open yshui opened this issue 4 years ago • 4 comments

I tried this code:

use either::Either;
struct A;
struct B;
struct AW<'a>(&'a mut A);
impl<'a> Drop for AW<'a> {
    fn drop(&mut self) {

    }
}
fn cap(_: &mut A) -> Either::<AW<'_>, B> {
    unimplemented!()
}
pub fn main() {
    let mut a = A;
    let b = cap(&mut a);
    match b {
        Either::Left(a) => {
            drop(a);
        },
        Either::Right(y) => {
            a = A; // <- doesn't compile
        }
    }; // No way is the compiler going to drop b at this point in either case
}

In this piece of code, the compiler keeps b alive for the entire duration of match, preventing me from assigning to a in the Right arm. The explanation it gives is that it needs to run drop on b at the end of the match.

error[E0506]: cannot assign to `a` because it is borrowed
  --> src/main.rs:22:13
   |
15 |     let b = cap(&mut a);
   |                 ------ borrow of `a` occurs here
...
22 |             a = A;
   |             ^^^^^ assignment to borrowed `a` occurs here
...
26 | }
   | - borrow might be used here, when `b` is dropped and runs the destructor for type `Either<AW<'_>, B>`

However, that's not the case at all. b is partially moved in both arms, its drop cannot be called at the end of match.

Meta

rustc --version --verbose:

rustc 1.50.0-nightly (f76ecd066 2020-12-15)
binary: rustc
commit-hash: f76ecd0668fcdb289456cdc72a39ad15467cc454
commit-date: 2020-12-15
host: x86_64-unknown-linux-gnu
release: 1.50.0-nightly

yshui avatar Feb 03 '21 06:02 yshui

I think this is a well-known borrow-checker bug — see https://rust-lang.zulipchat.com/#narrow/stream/122651-general/topic/borrow.20error.

@rustbot label: T-compiler

camelid avatar Feb 05 '21 00:02 camelid

Update based on discussion happened on zulip.

Some suggested using polonius, but the example code doesn't compile with polonius enabled either.

@danielhenrymantilla reduced it to

struct A<'a>(&'a i32);

impl<'a> Drop for A<'a> {
    fn drop (&mut self)
    {}
}

fn main ()
{
    let mut x = 10;
    let r = (A(&x), );
    let a = r.0;
    drop(a);
    x = 11; // Ok if A wasn't Drop
}

So there needn't be a match to trigger this problem.

Quoting:

No branches, and it still fails (so I really doubt it's polonius). The key thing is that dropping a field (even if it's the only field!) of a struct / enum does not seem to tag the wrapper as consumed, dropck-wise, even if, at first glance, it looks to me like it could.

yshui avatar Sep 13 '22 21:09 yshui

For people stumbling across this looking for a workaround, one option is putting the Drop variable in a ManuallyDrop, and make sure to drop it manually.

Using this as an example:

use std::task::Poll;
struct A<'a>(&'a i32);
impl<'a> Drop for A<'a> {
    fn drop(&mut self) {}
}

fn main() {
    let mut x = 10;
    let r: Poll<_> = Poll::Ready(A(&x));
    match r {
        Poll::Ready(a) => {
            drop(a);
            x = 11; // Ok if A wasn't Drop
        }
        Poll::Pending => {}
    }
}

Change it to:

use std::task::Poll;
use std::mem::ManuallyDrop;
struct A<'a>(&'a i32);
impl<'a> Drop for A<'a> {
    fn drop(&mut self) {}
}

fn main() {
    let mut x = 10;
    let r: Poll<_> = Poll::Ready(ManuallyDrop::new(A(&x)));
    match r {
        Poll::Ready(a) => {
            ManuallyDrop::into_inner(a);
            x = 11;
        }
        Poll::Pending => {}
    }
}

yshui avatar Sep 13 '22 21:09 yshui

Another workaround is to not partially move out of b and unconditionally drop b in every match arm.

    match b {
        Either::Left(AW(&mut ref mut a)) => { // or just Either::Left(_)
            drop(b);
        },
        Either::Right(ref y) => { // just Either::Right(_)
            drop(b);
            a = A;
        }
    };

Playground.


I wouldn't call this a bug in the borrow checker. The borrow checker can't determine where drops / drop glue runs; it is given that information (or tentative information). I'd say it's a shortcoming of the drop checker (or whatever it is that determines a value which has been unconditionally moved doesn't need drop glue at the end of scope). Presumably with regards to partial moves and/or moves out of enum variants "adding up" to an unconditional move.

QuineDot avatar Mar 10 '24 20:03 QuineDot