How to forget a object "pinned" by self reference?

Consider the following minimal example with a self referencing struct:

use std::cell::Cell;
struct Cyclic<'a> {
    self_ref: Cell<Option<&'a Cyclic<'a>>>,
}

We can create one which references itself without any problem:

let x = Cyclic { self_ref: Cell::new(None) };
x.self_ref.set(Some(&x));

But x is now borrowed for its whole lifetime, and therefore it cannot be moved, which is quite reasonable. However, since std::mem::forget requires a move, I cannot think of a way to leave the scope without dropping x.

Now certainly the example looks silly in its presented form, because it does nothing when dropped anyway. However, despite that the cyclic reference prevents custom Drop implementations, Cyclic may still drop its fields normally:

struct Cyclic<'a> {
    payload: Box<[u8; 1_000_000]>,
    self_ref: Cell<Option<&'a Cyclic<'a>>>,
}

I think it makes sense now to forget a Cyclic in order to leak the Box.

I thought it might be a good place to use ManuallyDrop, paying the price of unsafe for the functionality. But the opposite happens: it cannot be dropped, because ManuallyDrop::drop requires taking a mutable reference. According to the documentation, ManuallyDrop::drop is equivalent to calling ptr::drop_in_place on a pointer to the contained value, so I think the way to go here is to take a reference of the ManuallyDrop, cast it to a pointer, then to a pointer of Cyclic, and finally call ptr::drop_in_place on it.

My questions are:

  1. Is my interpretation of the documentation correct, i.e., is it legal to do the following?
    let x: ManuallyDrop<Cyclic> = /* ... */;
    ptr::drop_in_place(
        &x as *const ManuallyDrop<Cyclic>
            as *const Cyclic
            as *mut Cyclic
    );
    
    The documentation for std::ptr::drop_in_place mentions that the pointer must "be valid for both reads and writes", which I am afraid cannot be satisfied, because it comes from a shared reference. Does that mean I also need to wrap it in UnsafeCell?
  2. Not calling the drop glue of any object ("leaking" an object in the general sense) is usually considered safe, but it seems there is no way (safe or unsafe) to leak x as defined above. Is it an oversight in language design that std::mem::forget relies on a move? Or in other words, would it be better if it were a keyword like del in Python (but of course without calling its drop glue)? If so, is it possible that future versions of Rust includes something like this?
  3. What alternatives may I use?

No, how else could it possibly work without a move? If mem::forget() didn't move its operand, it would still be dropped by the normal scoping rules at the end of its scope.

Don't use lifetime-based self-references. Try Rc/Weak instead.

Well, do you want to leak it or do you want to drop it? The way you leak with ManuallyDrop is to not call ManuallyDrop::drop.

Or you could just pre-leak the payload.

If there were a language built-in construct, it could work by marking the variable as having been moved out of, without actually moving. Semantically, accessing the variable afterwards is prohibited, and the drop glue is not called. It is safe because leaking is always safe. But of course, this could only be done by compiler magic. I mentioned the del keyword in Python specifically to make an analogy, and I also explicitly asked the following:

The actual problem is about having a typed_arena::Arena with mutual references between the objects in the arena (see this example). Putting objects containing references to other objects into the arena effectively makes the arena borrow itself. I hope you agree that arenas are valid use cases in Rust, and it is not an option to switch back to Rc/Weak.

The idea was to conditionally leak x, possibly according to the contents of x. It might be a weird requirement, though.

In that case you're probably better off taking a &mut as *mut _ as *mut Cyclic from the ManuallyDrop to begin with, routing all access through the *mut, and ensuring the definitely-last use of any borrow through the *mut is the call to drop_in_place.

Something like...

    let mut x = ManuallyDrop::new(Cyclic { ... });
    let mx = &mut x as *mut _ as *mut Cyclic;
    let rx = unsafe { &*mx };

    // ------------------------------------------+
    // Only use `rx` in this section, and don't  |
    // use `rx` or anything derived from it      |
    // after this section if you will drop       |
    rx.self_ref.set(Some(rx)); //                |
    // ------------------------------------------+

    if you_should_drop {
        unsafe { ptr::drop_in_place(mx); }
        // Don't use `x` or `mx` or anything
        // derived from them after this point
    }

This says nothing about some other piece of code doing anything with the to-be-leaked data, which may also be UB depending on what it does when (given the existence of rx).

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.