Review soundness of an operation replacing a pinned value

In the spirit of take_mut but for pinned values:

#![no_std]
use core::pin::Pin;

/// Drop a pinned value early, replacing it with another.
///
/// There's no use making `val: T` as there's a panic path that needs to be aborted in any case.
/// This allows the `Drop` to return some buffers _before_ the closure is invoked, i.e. the new
/// task might reuse some resources just returned by the old task.
pub fn replace_pin_with<T>(pin: Pin<&mut T>, val: impl FnOnce() -> T) {
    struct AbortOnPanic;

    impl Drop for AbortOnPanic {
        fn drop(&mut self) {
            // We only enter this on a panic, so this double panics, i.e. aborts.
            panic!()
        }
    }

    // Ensure our local panic unwind is unreachable. We temporarily leave objects invalid and must
    // not return while in that state.
    let _aborter = AbortOnPanic;

    // Pointer to the inner value. Note: huh. Really this should be one operation with no
    // intermediate, alias-asserting, mutable unique reference. However, there is no such defined
    // operation in the standard library. Really curious.
    let value = unsafe { pin.get_unchecked_mut() } as *mut _;
    // Drop the value in-place, fulfilling the `Pin` contract early. Will return a valid value to
    // the caller by refilling the slot just below.
    unsafe { core::ptr::drop_in_place(value) };

    let new_value = val();
    // The caller guarantees that it will be dropped before the memory is reused. Se it's okay we
    // pin this new value by writing it to pinned memory.
    unsafe { core::ptr::write(value, new_value) };

    // Success, no need to abort.
    core::mem::forget(_aborter);
}
  • Is this considered sound up to RFCs?
  • Is this consistent with the current noalias assertions actually emitted for Pin?
  • Should it?

I'm not sure what this comment is trying to say:

/// There's no use making `val: T` as there's a panic path that needs to be aborted in any case.

If it were val: T then this would just be Pin::set. Using that function as prior art, the only difference here is that the drop and write operations are separated with the closure running in between.

6 Likes

Good point, I hadn't realized that assignment was simpler indeed with no abort path. If we had FnPure this would be quite interesting to use instead.

Thanks, then there's nothing too worrying going on here.