What is the "ideal" way to destruct a guard type?

Suppose i have a Guard type, which will notify its inner value that it is being dropped:

struct Guard(S);
impl Drop for Guard {
   fn drop(&mut self) {
       self.0.clean_up()
   }
}

However under a special case, i'll need to move and transfer the ownership of S to another guard type, who will in turn do this clean up job for S.

The problem is: How to write this transfer function? I know i can use std::mem::forget or something to achieve the effect i want, but i'm not sure what's the best way to do so?

Is there a reason why S can't clean up in its own Drop implementation?

Otherwise, you could make S optional in Guard:

struct Guard(Option<S>);

Then your transfer function can use self.0.take().

1 Like

You would want your transfer function to take self and manually move S into the new guard. I've whipped up an example here:

I also wonder if it would be better for S to have its own destructor, but the general idea of ownership transfer should be similar in that case too.

2 Likes

You probably don't need a "transfer function". Moving a value transfers ownership automatically.

And you move a Guard value the same way you'd move anything else: pass it to a method, return it to the caller, assign it to a new variable, push it to a Vec, etc. In your case, maybe you just want to move the Guard to a field of the other guard type you mentioned.

    let guard = Guard(s);
    ...
    let other_guard = OtherGuard { inner: guard };  // transfers `guard`

Guards are more fluid in Rust than in C++.

Since you can move a guard from a local variable to someplace else, it's super easy to defer the .drop() call and still be confident that it'll happen at the right time.

1 Like

There's a pattern that might be relevant for your case. You can see an example of it in the source for RefCell.

Ref does not actually implement Drop. Instead it contains a BorrowRef, a special private type which does implement Drop. Why this apparently unnecessary layer of indirection?

Because of map. In map the guard orig is destructured by pattern match, and its BorrowRef is repackaged. This ends the life of the first guard and begins another without invoking BorrowRef's Drop.

This kind of destructuring by pattern match (the kind that ends its life, as opposed to obtaining a reference by pattern match) is not permitted for types that implement Drop. By moving the Drop impl onto BorrowRef the code becomes legal.

This sounds like the sort of application you're hinting at, so I hope that helps.

4 Likes

The ideal is probably unstable ManuallyDrop::take

With ManuallyDrop you will also need to remember whether you already took the value out, or still need to drop it. So you add a "tag" indicating Some or None... :slight_smile:

1 Like

I feel like Option and ManuallyDrop are both ill-suited for that reason. They force every piece of code working on the inner type to check for present-or-absent, or dropped-or-not respectively. Meanwhile the actual semantics of the Guard type is (presumably) that its inner value is always valid as long as the Guard is valid, and that story only gets fuzzy when handing off ownership in a self-consuming function. So I think it's fine to just use a bit of unsafe during that handoff instead of encoding optionality into every interaction with the contained value.

use core::{mem, ptr};

pub
trait CleanUp {
    fn clean_up (&mut self);
}

pub
struct CleanOnDrop<T : CleanUp> (T);

impl<T : CleanUp> Drop for CleanOnDrop<T> { fn drop (&mut self) {
    self.0.clean_up();
}}

impl<T : CleanUp> CleanOnDrop<T> {
    /// Extracts the inner value, without triggering any clean_up
    pub
    fn into_inner (mut self) -> T
    {
        unsafe {
            let inner = ptr::read(&mut self.0);
            mem::forget(self);
            inner
        }
    }
}
3 Likes