Tiny wrapper for partial pinning

When building primitives for futures (like Inspect), we often need partial pinning. I think this is done by first defining which fields we are pinning, and then apply this to the Unpin impl and projection helpers. In the Inspect example, we’re pinning the future field, but f not.

However, although partial pinning is defined for each field in programmers’ mind, they instead implicitly appear in the implementations. I see this as a possible footgun.

Isn’t it clever to provide this tiny wrapper for partial pinning? It’s easy to use: you wrap non-pinned fields using this. You don’t need to carefully implement Unpin. Pin projections simply work. I’d name it Kite because a kite floats around even when its root is pinned.

#![feature(pin)]

use std::ops::{Deref, DerefMut};
use std::pin::Unpin;

pub struct Kite<T: ?Sized> {
    inner: T,
}

impl<T: ?Sized> Unpin for Kite<T> {}

impl<T> Kite<T> {
    pub fn new(inner: T) -> Self {
        Self { inner }
    }

    pub fn into_inner(self) -> T {
        self.inner
    }
}

impl<T: ?Sized> Deref for Kite<T> {
    type Target = T;
    fn deref(&self) -> &Self::Target {
        &self.inner
    }
}

impl<T: ?Sized> DerefMut for Kite<T> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.inner
    }
}

Usage

This is taken from the Inspect example above.

pub struct Inspect<Fut, F> where Fut: Future {
    future: Fut,  // This is a pinned field
    f: Option<F>,  // This is a non-pinned field
}

impl<Fut: Future, F: FnOnce(&Fut::Output)> Inspect<Fut, F> {
    // `unsafe_pinned!` for pinned fields, `unsafe_unpinned!` for non-pinned fields.
    unsafe_pinned!(future: Fut);
    unsafe_unpinned!(f: Option<F>);
}

// Require `Unpin` for pinned field types.
impl<Fut: Future + Unpin, F> Unpin for Inspect<Fut, F> {}

With Kite, you can just wrap f.

pub struct Inspect<Fut, F> where Fut: Future {
    future: Fut,
    f: Kite<Option<F>>,
}

impl<Fut: Future, F: FnOnce(&Fut::Output)> Inspect<Fut, F> {
    // No difference here
    unsafe_pinned!(future: Fut);
    unsafe_pinned!(f: Kite<Option<F>>);
}

// No need to implement `Unpin` manually

One pitfall is that when doing pin projection, someone may accidentally go across the Kite barrier.

An alternative would be to just require an unpinnable closure and rely on the impl DerefMut for Pin<P> where P: DerefMut, P::Target: Unpin:

pub struct Inspect<Fut, F> where Fut: Future {
    future: Fut,  // This is a maybe-pinned field
    f: Option<F>,  // This is a non-pinned field
}

impl<Fut: Future, F: FnOnce(&Fut::Output) + Unpin> Inspect<Fut, F> {
    // `unsafe_pinned!` for all fields
    unsafe_pinned!(future: Fut);
    unsafe_pinned!(f: Option<F>);
}

// use `self.f().take()` which works because of `DerefMut`.

I found the original post a bit confusing at first, so I'll restate the proposition: the type called Kite here is a wrapper around T: ?Unpin that implements Unpin. Kite does not allow pin projections, so you can never observe a pinned pointer to T. This means you can wrap generic types in a Kite instead of having to add the Unpin line.

IMO, adding the Unpin line isn't really more work than wrapping the relevant types in an unpin wrapper. However, either way this is the sort of code that belongs in pin-utils, so the best place to continue this proposal is the pin-utils crates' issue tracker.

This is strictly worse though: there's no reason you cant accept a closure that does not implement Unpin here.

1 Like

What use does an impl FnOnce + !Unpin have? It gives no additional capabilities to the implementor over an impl FnOnce + Unpin.

It’s getting pretty off-topic here, but I think all closures should have an unconditional implementation of Unpin added automatically, closing over a !Unpin value shouldn’t cause the closure to become !Unpin since there’s no way for the closure to soundly use this fact.

Its rather absurd but still possible for something to implement the FnOnce API while having a separate API that is only valid if it is pinned. As an example, someone could manually implement a self-referential generator type of state machine which can be run entirely through using the FnOnce API, which doesn’t require pinning because it never pauses to yield and therefore never saves its state.

EDIT: To be more concrete, you could imagine some kind of wait wrapper around an F: Future which implements FnOnce by polling in a loop until the future returns ready. It doesn’t actually need to be pinned externally to be safe.

Ah pin-utils! I couldn't come to that but that seems a good place. Maybe I'll file a proposal there.

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.