Idea: `with` block to ensure definite resource release

Pin does have its Drop guarantee so it can be used in this way sometimes. Unfortunately the guarantee is not strong enough for APIs such as thread::scope since it only guarantees that data owned by the pinned pointer won't be invalidated before the pointer is dropped. It doesn't guarantee that borrowed data will remain valid until the pointer is dropped.

In the following code for example it would not be sound to allow other threads access to the borrow reference by assuming we could join in the Drop impl:

use core::marker::PhantomPinned;
use core::pin::Pin;

struct Guard<'a> {
    value: u32,
    borrow: &'a u32,
    pinned: PhantomPinned,
}
impl<'a> Guard<'a> {
    pub fn new(value: u32, borrow: &'a u32) -> Self {
        Self {
            value,
            borrow,
            pinned: PhantomPinned,
        }
    }
    pub fn start_background(self: Pin<&mut Self>) {
        // Send pinned values elsewhere (such as another thread)
        // We could send a pointer to `value` but not to `borrow`.
        println!("Start background")
    }
}
impl<'a> Drop for Guard<'a> {
    fn drop(&mut self) {
        // Drop guarantee ensures this is called before `Self` is deallocated.
        // It doesn't guarantee that this is called before `borrow` becomes invalidated since it is not owned by `Self`.
        println!("might never run if `Self` is forgotten");
    }
}

fn main() {
    let value = 2;
    let mut guard = Box::pin(Guard::new(4, &value));
    guard.as_mut().start_background();
    // Drop is never called (but we don't free the guard, so we don't break the "drop guarantee")
    core::mem::forget(guard);
}

fn alt_main() {
    let value = 2;
    let scope = async {
        // This could be in a separate async function:
        let guard = Guard::new(4, &value);
        tokio::pin!(guard);
        guard.start_background();
        // Yield:
        futures::future::pending::<()>().await;
    };
    let mut scope = Box::pin(scope);
    // Run to the yield point of the scope:
    futures::FutureExt::now_or_never(scope.as_mut());
    // Ensure drop impls after the yield point isn't run:
    core::mem::forget(scope);
}

Playground

1 Like