Skip to content

What to do if the destructor of a pinned field panics? #232

Closed
@Diggsey

Description

@Diggsey

Let's say I have an enum, with a variant containing some structurally pinned fields:

#[pin_project]
enum Foo {
    A {
        #[pin] pinned_field1: X1,
        #[pin] pinned_field2: X2,
        #[pin] pinned_field3: X3,
        unpinned_field: Y,
    },
    B,
}

Given a Pin<&mut Foo>, I would like to do something like mem::replace(pinned_foo, Foo::B).

Now, in general this would not be allowed, because it would move the pinned fields. However, I should be able to drop the pinned fields in-place, take the unpinned field, and then overwrite the entire value with Foo::B.

One way of doing this is just using the relevant ptr::{read, write, drop_in_place} methods. The problem is that the destructors of the pinned fields might panic, in which case it's unclear what I would need to do to avoid causing UB.

Firstly, if the destructor of a pinned field panics, am I allowed to reuse that memory ever? The contract for Pin says that the destructor must run before the memory is reused, but it doesn't say whether it has to complete successfully.

From what I can tell, the optimal method would be to create structs like this:

struct DropInPlaceHelper<T>(*mut T);

impl<T> DropInPlaceHelper<T> {
    fn drop(&mut self) {
        ptr::drop_in_place(self.0);
    }
}

struct OverwriteHelper<T>(*mut T, MaybeUninit<T>);

impl<T> OverwriteHelper<T> {
    fn drop(&mut self) {
        ptr::write(self.0, self.1.assume_init());
    }
}

And then construct instances of it on the stack for each field:

fn partial_replace(ptr: *mut Foo, new_value: Foo) -> Y {
    let _helper0 = OverwriteHelper(ptr, MaybeUninit::new(new_value));
    let res = ptr::read(&mut (*ptr).unpinned_field);
    let _helper1 = DropInPlaceHelper(&mut (*ptr).pinned_field1);
    let _helper2 = DropInPlaceHelper(&mut (*ptr).pinned_field2);
    let _helper3 = DropInPlaceHelper(&mut (*ptr).pinned_field3);
    res
}
  1. Would this be valid?
  2. Is there a better/more ergonomic way of doing this? (that doesn't require changing the enum definition)

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-dropTopic: related to droppingA-unwindTopic: related to unwindingC-supportCategory: Supporting a user to solve a concrete problem

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions