1

Let's say I've got a struct that contains a pinned pointer to a boxed T and is Drop:

struct S<T> {
    t: Pin<Box<T>>,
}

impl<T> Drop for S<T> {
    // ...
}

How do I implement the following function without needing to add an otherwise-unnecessary Option layer? Unsafe code is fine.

impl<T> S<T> {
    /// Release the handle to the `T` object, consuming the struct so it
    /// isn't dropped.
    fn into_t(self) -> Pin<Box<T>> {
        todo!();
    }
}

The basic challenge is that Pin<Box<T>> has no "missing" representation so can't directly be taken. I can think of ways to do it with ManuallyDrop and type punning, but I'm not 100% sure that the type punning is guaranteed to be okay. I'm looking for either an API that does this directly, or citations for memory layout that say it's okay to do whatever tricks the answer contains.

5
  • In Rust there is no such thing as a value that can't be taken — every type can be moved, period (and when consuming an owning object, you don't need to leave anything in its place). What Pin prevents is moving the pointed-to value, in this case the thing the Box points to. Commented Nov 24 at 3:06
  • Yes, sorry, I'm looking for a version that works even when S is Drop. Thanks, will clarify in the question. Commented Nov 24 at 3:07
  • You can use ManuallyDrop like this: stackoverflow.com/a/74914046/2189130 I'm not sure what you mean by type punning Commented Nov 24 at 3:14
  • I'm hoping not to make the member itself ManuallyDrop. The type punning would be something like creating a local ManuallyDrop<S>, then using a pointer cast to access that as a ManuallyDrop<Pin<Box<T>>, and take from that. Commented Nov 24 at 3:17
  • ManuallyDrop doesn't require type punning, it's perfectly fine to use ManuallyDrop<Pin<Box<T>>: play.rust-lang.org/… (Not posting this as answer, because jacobsa's approach results in the same effect, but with much less code, unless you're scared by std::ptr::read().) Commented Nov 25 at 10:39

1 Answer 1

3

Here is an implementation that uses std::ptr::read:

impl<T> S<T> {
    fn into_t(self) -> Pin<Box<T>> {
        // Prevent self from being dropped so that the box isn't destroyed
        // twice - and because the question explicitly requests drop() not
        // to run when into_t() is invoked.
        let me = ManuallyDrop::new(self);

        // Load the value of `me.t` without needing to take it.
        //
        // Safety: this pointer is valid by construction, because
        // ManuallyDropped doesn't affect layout or bit patterns.
        unsafe { std::ptr::read(&raw const me.t) }
    }
}

(Playground)

Sign up to request clarification or add additional context in comments.

6 Comments

Does this leak the other fields of self (if there are any)?
Potentially, it does free all the memory the fields occupy directly (for example the memory used for the pointer and metadata in a Box), but it will not call Drop::drop on them so it will not deallocate or clean up any indirect resources (i.e. it will not free the allocation of a Box).
Yes, though it's a bit unclear whether that's requested by the question. The code comment in the question specifies "so it [S] isn't dropped", which might refer to the whole drop glue of S, or just to the logic in S::drop(). This could be fixed by grouping the non-t fields into a separate structure, and dropping it while returning self.t and forgetting self: play.rust-lang.org/…
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.