[Pre-RFC] pattern matching std::pin::Pin, converting &mut T to Pin and narrowing scope

When looking at std::pin::Pin it seemed that following operations should be safe - narrowing scope, converting &mut T into Pin<&mut T> and pin pattern matching.

struct Map<F, G> {
    future: F,
    func: Option<G>
}
impl<F, G, B> Future for Map<F,G>
where
    F: Future,
    G: FnOnce(Future::Output) -> B + Unpin
{
    type Output = B;
    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>
    {
        // future: Pin<&mut F>
        // func: &Option<FnOnce(Future::Output) -> B>
        // To have lack of pin it needs to be Unpin
        let Map {pin ref mut future, ref mut func} = self;
        match future.poll(cx) {
            Ready(value) => Ready(func.take().expect("Map value already consumed")(value)),
            Pending => Pending.
        }
    }
}

Converting &mut T into Pin<&mut T> - since &mut T is an exclusive reference to T any access to T needs to be done by this reference. So we can temporarily pin it as we know no-one will move the value from underneath us:

impl<'a, T> Pin<&'a mut T> {
    pub fn new_from_mut(&'a mut T) -> Self;
}

Narrowing scope (Pin is contravariant in scope):

impl<'a> Pin<&'a T> {
    fn narrow<'b>(&'mut self) -> Pin<&'b T> where 'a: 'b
}

If data is unmovable in larger scope it is also unmovable in smaller scope.

Pattern matching - currently field access is under unsafe. However this seems to be unnecessarily restrictive. However we already have a pattern matching on references so pin might make sense:

struct FlatMap<F, G, H> {
    BeforeF(F, Option<G>),
    BeforeH(H)
}

impl<B> Future for FlatMap<B, F, G, H>
where
    F: Future<Output = B>,
    G: FnOnce(B) -> H + Unpin,
    H: Future
where
{
    type Output = H::Output;
    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>
    {
        let new_h_opt = if let FlatMap::BeforeF(pin ref mut future, ref mut func) = self.narrow() {
            match future.poll(cx) {
                Ready(value) => Some(func.take().unwrap()(value)),
                Pending => return Pending;
            }
        } else {
            None
        };
        if let Some(new_h) = new_h_opt {
            self.set(FlatMap::BeforeH(new_h));
        }
        if let FlatMap::BeforeH(pin ref mut future) {
            future.poll(cx)
        } else {
            unreachable!()
        }
    }
}

I don't see any related proposals and my (very basic) understanding of Pin indicates those operations should be safe

1 Like

You can't do this, once a value is pinned, it must remain pinned for the rest of it's lifetime (until it's dropped)

From the pin module docs (emphasis mine)

At a high level, a Pin<P> ensures that the pointee of any pointer type P has a stable location in memory, meaning it cannot be moved elsewhere and its memory cannot be deallocated until it gets dropped

You can also use

4 Likes

(Unless it implements Unpin.)

2 Likes

Regarding projections, every type can decide which fields it pins and which not. It may leave some fields unpinned even if they don't implement Unpin. If a field is left unpinned, it is just not allowed to take an Pin<&mut T> for that field in that case. If the field is pinned and not Unpin it is not allowed to take an &mut T unless it isn't exposed to the user and the pinning invariants are maintained.

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