Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it safe to implement `std::marker::Unpin`?

Tags:

rust

future

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.

like image 394
ensc Avatar asked Dec 21 '19 15:12

ensc


People also ask

Why pin rust?

If you have a Pin -ned pointer to some data, Rust can guarantee that nothing unsafe will happen (if it's safe to move, you can move it, if it's unsafe to move, then you can't). This is important because many Future types are self-referential, so we need Pin to safely poll a Future.

What is unpin rust?

pub auto trait Unpin { } Types that can be safely moved after being pinned. Rust itself has no notion of immovable types, and considers moves (e.g., through assignment or mem::replace ) to always be safe. The Pin type is used instead to prevent moves through the type system.


1 Answers

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
    }
}
like image 148
edwardw Avatar answered Sep 21 '22 14:09

edwardw