Why do non-lexical lifetimes not work in this case?

The following code fails to compile:

struct Foo<'a, T>(&'a mut T);

impl<'a, T> Drop for Foo<'a, T> {
    fn drop(&mut self) {
        println!("Dropping");
    } 
}

fn main() {
  let mut x = 5;
  let _y = Foo(&mut x);
  println!("{:?}", x);
}

(Playground)

I would expect it to work due to non-lexical lifetimes—after all, _y is never used and could go out of scope immediately. Even more confusing is the fact that if I either

  • remove the Drop implementation for Foo or
  • rename _y to _

it works. What is going on here?

2 Likes

It's because NLL doesn't affect when a drop occurs so _y is still dropped at the end of the scope, if you manually drop it, it works

fn main() {
    let mut x = 5;
    let _y = Foo(&mut x);
    drop(_y);
    println!("{:?}", x);
}

Also if you assign to _ it will just drop it immediately.

8 Likes

Also if you assign to _ it will just drop it immediately.

And the reason for this is that _ is not an identifier name - it's a reserved token used in patterns, and the pattern is specifying that you don't wish to bind the value in the pattern, causing it to be dropped immediately.

5 Likes

This is the piece I was missing, thank you. Would it be correct to say that under non-lexical lifetimes, _y can go out of scope at any point in a block, but the actual drop call only ever happens at the end of a block? Which would mean that if the drop method is nontrivial, _y has to live to the end of the block so that drop still has it available when it needs to do its thing.

1 Like

Under NLL, the borrow (i.e., just a compile-time property) is released as soon as it is no longer used.
The variable holding the pointer (runtime representation of the reference) is, however, still dropped at the end of its lexical scope.

Thus, if the variable holding the borrow has Drop (glue), the borrow may be accessed / used when the variable is drop-ped, meaning that the no longer used moment happens after the end of the lexical scope, rendering NLL useless in this case indeed.

5 Likes

Thank you, that explains it pretty well. The distinction between a borrow at compile time and a variable at runtime is a bit subtle!

1 Like

Something that this other thread made me realise:

This can be avoided with the unsafe (and unstable) #[may_dangle] attribute:

#![feature(dropck_eyepatch)]

pub
struct Foo<'a, T> /* = */ (&'a mut T);

unsafe impl<#[may_dangle] 'a, T : 'a>
    Drop for Foo<'a, T>
{
    fn drop (self: &'_ mut Self)
    {
        // this does not dereference self.0 (it just prints the raw address of the pointee), so it is fine
        println!("Dropping Foo({:p})", self.0);
    } 
}

fn main ()
{
    let mut x = 5;
    println!("x at {:p}", &x);

    let foo = Foo(&mut x);
    println!("Created Foo(&mut x = {:p})", foo.0);

    println!("Accessing x directly: {}", x);
}

Which outputs:

x at 0x7fff656e20d4
Created Foo(&mut x = 0x7fff656e20d4)
Accessing x directly: 5
Dropping Foo(0x7fff656e20d4)

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.