Forgetting stuff inside drop

Hi,

it is possible to leak/forget stuff inside drop?

Ideally I would like to have this playground working somehow: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=01a13fbb1d711d9f95b2fd3d3dfdc789

I want to forget about the value in Foo.bar , how is that possible?

#![allow(dead_code)]

struct Bar {}

struct Foo {
    bar: Bar,
}

impl Drop for Foo {
    fn drop(&mut self) {
        std::mem::forget(self.bar);
    }
}

Which give:

error[E0507]: cannot move out of `self.bar` which is behind a mutable reference
  --> src/lib.rs:11:26
   |
11 |         std::mem::forget(self.bar);
   |                          ^^^^^^^^ move occurs because `self.bar` has type `Bar`, which does not implement the `Copy` trait

It's not possible in this way, instead, find ManuallyDrop and that's the way to do it, it should work fine.

2 Likes

No, ManuallyDrop doesn't help neither.

ManuallyDrop to be constructed need a full type, while all I got is a raw pointer.

I could read the pointer and get the full type, but then I would need to write back the type, ideally in the destructor, which yet again is not allowed.

If you have some no-op value of Foo, like an empty Vec, you can mem::replace it and forget the old one.

A raw pointer on its own doesn't do anything for Drop, but I suppose your real code is more than that. I don't think we have enough information to help with this more complicated situation.

Yep, but a raw pointer need to be de-referenced all around the place polluting the codebase a lot.

// in real life Bar is much more complex
struct Bar {
    n: i32,
}

struct Foo {
    bar: Bar,
}

impl Drop for Foo {
    fn drop(&mut self) {
        std::mem::forget(self.bar);
    }
}

impl Foo {
    fn upgrade_bar(&mut self) {
        self.bar.n += 1;
    }
}

fn get_raw_to_bar() -> *mut Bar {
    // magic outside our control
}

fn main() {
    {
        let bar = get_raw_to_bar();
        let f = Foo{bar};
        f.upgrade_bar();
    } // drop Foo, while leacking bar
    
    {
        let bar = get_raw_to_bar(); //get the same pointer to Bar as above, not a new instance
        let f = Foo{ bar };
        f.upgrade_bar();
    }
    
    // now Bar.n should be two units bigger than at the beginning
    
}

The correct and practical way is to modify the definition of Foo to contains bar: ManuallyDrop<Bar>. ManuallyDrop impl Deref[Mut] so most of your code will not even be modified at all.

1 Like

This is missing a line:

let bar = get_raw_to_bar();
let bar = unsafe { bar.read() };
let f = Foo{bar};
f.upgrade_bar();

which is easily changed to

let bar = get_raw_to_bar();
let bar = unsafe { (bar as *mut ManuallyDrop<Bar>).read() };
let f = Foo{bar};
f.upgrade_bar();

Of course, this is besides your point that the original Bar is not modified. I'll get to that. I bring it up just because I'm still confused over what you are actually doing, due to this quote:

If you aren't reading the pointer at any time, then why are you worried about destructors in the first place?

It seems to me that you want your type to semantically be:

struct Bar {
    n: i32,
}

struct Foo {
    bar: &'static mut Bar,
}

In which case a problem becomes very immediately evident: That whenever you have two Foos in scope produced in the manner you suggest, the bar fields will be aliased. Unless there is some locking mechanism in place to prevent that from happening, this makes the desired code in main appear suspect.