Is it safe to implement `std::marker::Unpin`?
Asked Answered
S

1

7

I am about to convert some code from futures-0.1 to futures-0.3 where the poll() methods require pinned data now. Some of my structs are unpinnable which complicates the porting. But there seems to exist an easy way by adding an impl Unpin for these classes. Is this safe? What are the alternatives?

Example code:

extern crate futures;

use std::future::Future;
use std::pin::Pin;
use std::task::{ Poll, Context };

struct InnerData {
    _pin: std::marker::PhantomPinned,
}

struct Stream {
}

struct Poller {
    _data: InnerData,
    file: Stream,
}

impl futures::stream::Stream for Stream {
    type Item = ();

    fn poll_next(self: Pin<&mut Self>, _cx: &mut Context) -> Poll<Option<Self::Item>> {
        Poll::Pending
    }
}

impl Future for Poller {
    type Output = Result<(), ()>;

    fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output>
    {
        use crate::futures::Stream;

        // here, rust fails with
        // error[E0277]: the trait bound `std::marker::PhantomPinned: std::marker::Unpin` is not satisfied in `Poller`  
        Pin::new(&mut self.get_mut().file).poll_next(cx).is_ready();

        Poll::Pending
    }
}

// Activating this seems to be an easy workaround...
// impl std::marker::Unpin for Poller {}

fn main() {
}

I use futures-0.3.1 and rust-1.40.0.

Saville answered 21/12, 2019 at 15:25 Comment(1)
I'd dare say that if this weren't safe, the trait itself would be unsafe, as Send or Sync are, but I also struggle to understand all this Pin business.Zlatoust
N
1

Is this safe?

Yes because marking Poller as Unpin is not transitive with regard to its fields. You still can't conjure a pinned _data field out of thin air. It is a compiler error if you try Pin::new(&mut self.get_mut()._data) since new is only available for Pin<P> if <P as Deref>::Target is Unpin:

impl<P> Pin<P> where
    P: Deref,
    <P as Deref>::Target: Unpin,
{
    pub fn new(pointer: P) -> Pin<P>
}

The Rust Pin documentation has a section to expand on this:

Pinning is not structural for field

What that type thinks about pinning is not relevant when no Pin<&mut Field> is ever created.

It is only unsafe if you do impl std::marker::Unpin for InnerData {}, which promises the compiler something untrue. It won't be able to stop you from writing Pin::new(&mut self.get_mut()._data) any more.

So this works:

impl Future for Poller {
    fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
        use futures::stream::Stream as _;
        Pin::new(&mut self.get_mut().file).poll_next(cx).is_ready();
        Poll::Pending
    }
}

impl std::marker::Unpin for Poller {}

What are the alternatives?

There are third party crates such as pin-project to make this Pin business a tad easier:

use pin_project::pin_project;  // pin-project = "0.4.6"

#[pin_project]
struct Poller {
    _data: InnerData,
    #[pin]
    file: Stream,
}

impl Future for Poller {
    fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
        use futures::stream::Stream as _;
        let this = self.project();
        this.file.poll_next(cx).is_ready();
        Poll::Pending
    }
}
Note answered 22/12, 2019 at 14:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.