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
.
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.
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.
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
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With