Inhibit drop for an inner type

I have an "inner" type that can be transformed to a "public" type. I want to implement Drop traits for both of these, but the crux is that once the public object has been created, the Drop trait of the inner object should no longer run.

I.e.

struct Inner { }
impl Drop for Inner {
   fn drop(&mut self) {
    println!("Inner dropped!");
   }
}

struct Public { /* conceptually an Inner object lives in here */ }
impl Drop for Public {
   fn drop(&mut self) {
    println!("Public dropped!");
   }
}

When a Public object is dropped, I only want it to print "Public dropped!".

There's a From<Inner> for Public which transforms the Inner into a Public, what I'm trying to accomplish is to disable the Drop for the Inner once this happens. I realize I can use a semaphore variable to do this, but I'm wondering if there's some other way to do it.

Will running ManuallyDrop::new(inner) when constructing the Public object do the trick?

If Public actually contains an Inner, that contained Inner should be wrapped into a ManuallyDrop to prevent any drops from happening. So the approach depends a bit on what you mean by ”conceptiually”.. if you want to just get rid of an Inner value without drop being called, because Public does not actually contain an Inner then you can use ManuallyDrop::new(inner) and drop the result, or you can equivalently use std::mem::forget. Make sure that deleting Inner without calling the destructor does not permanently leak any resources though.

Just use std::rc::Rc for the shared ownership, unless you deal with performance-critical code. It'll handle the drop condition for you.

1 Like

How would do use std::mem::forget here? fn drop takes &mut Outer as an argument and therefore has no Ownership of Outer or Inner. You cannot call std::mem::forget without having the ownership.

I was having problems with OPʼs comment saying conceptually an Inner object lives in here. I don't know what the "conceptually" means here. The mem::forget call or the construction of a ManuallyDrop would happen while constructing Outer from the Inner, the mem::forget only being a viable option, when Outer actually doesn't really contain an Inner but e. g. only of its fields.

Still, even in these cases one might want to use ManuallyDrop, too, because it tends to be a bit easier to get the desired behavior in case of panics right.

1 Like

Can you provide a code example of what you mean?

Sure, I can. Since I was talking about at least 3 different cases

  • Inner object contained in outer, using ManuallyDrop
  • Inner object not contained in outer, using mem::forget
  • Inner object not contained in outer, using ManuallyDrop

it would be nice to know for what point exactly you'd like to see a code example.

Also note that also one important reason for bringing up mem::forget was that OP didn't mention it themself and it is one tool to keep in mind when trying to not call a destructor. It is even possible to implement your own safe ManuallyDrop replacement using Option and mem::forget.

Oh those 3 different cases work of course. I thought you meant something like this, which does not work and will never work.

impl Drop for Public {
   fn drop(&mut self) {
      println!("Public dropped!");
      mem::forget(self.member_variable_of_type_Inner);
   }
}

Just to clarify more in detail what I wanted to accomplish, and why:

I have a struct in a library, let's call it Inner which is used to communicate back a result to a function using a Condvar and a Mutex. This struct Inner exists on a queue. Once Inner has been taken off the queue it can either be processed directly, or it can be passed back to an application. BUT if it's passed to the application it needs to be put in another struct (Public), because that needs to implement Drop. Basically the idea is that if the Inner is handled over to the application, then it needs to signal back to the origin if it is dropped before being acted on.

I.e. I have an Inner which mostly lives on a queue. But when I take an Inner off the queue with the intention to pass it to an application, I put the Inner inside a Public object because I want a Drop implementation on Public.

The issue I ran into which caused this thread was that I realized that I wanted a Drop on Inner as well. BUT the Inner and Public drops are mutually exclusive in a sense: What I want is that if Inner is dropped without it being in a Public, then I want it to signal one thing back to the origin. If a Public is dropped I want to signal something else back to the original and not thing that is signaled back when Inner is dropped.

Does that make sense? I essentially want to signal back two different things to the origin depending on whether the Inner is by itself or if it has been "handed over" to a Public object.

This problem has trivial solutions using state variables in Inner, but I was trying to see if it was possible to use the type system to make Public inhibit Inner from calling my custom Drop implementation.

It just felt like one of those things that Rust would be able to do.