assert-unmoved 0.1.0

A type that asserts that the underlying type is not moved after being pinned and mutably accessed.
Documentation

assert-unmoved

crates-badge docs-badge license-badge rustc-badge

A type that asserts that the underlying type is not moved after being pinned and mutably accessed.

This is a rewrite of futures-test's AssertUnmoved to allow use in more use cases. This also supports traits other than futures.

Usage

Add this to your Cargo.toml:

[dependencies]
assert-unmoved = "0.1"

The current assert-unmoved requires Rust 1.37 or later.

Examples

An example of using Pin::new_unchecked incorrectly (should panic):

use futures_util::{
    future::{self, Future},
    task::{noop_waker, Context},
};
use assert_unmoved::AssertUnmoved;
use std::pin::Pin;

let waker = noop_waker();
let mut cx = Context::from_waker(&waker);

// First we allocate the future on the stack and poll it.
let mut future = AssertUnmoved::new(future::pending::<()>());
let pinned_future = unsafe { Pin::new_unchecked(&mut future) };
assert!(pinned_future.poll(&mut cx).is_pending());

// Next we move it back to the heap and poll it again. This second call
// should panic (as the future is moved).
let mut boxed_future = Box::new(future);
let pinned_boxed_future = unsafe { Pin::new_unchecked(&mut *boxed_future) };
let _ = pinned_boxed_future.poll(&mut cx).is_pending();

An example of incorrect StreamExt::next implementation (should panic):

use futures_util::{
    future::Future,
    stream::{self, Stream},
    task::{noop_waker, Context, Poll},
};
use assert_unmoved::AssertUnmoved;
use std::pin::Pin;

struct Next<'a, S: Stream>(&'a mut S);

impl<S: Stream> Future for Next<'_, S> {
    type Output = Option<S::Item>;

    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        // This is `Pin<&mut Type>` to `Pin<Field>` projection and is unsound
        // if `S` is not `Unpin` (you can move `S` after `Next` dropped).
        //
        // The correct projection is `Pin<&mut Type>` to `Pin<&mut Field>`.
        // In `Next`, it is `Pin<&mut Next<'_, S>>` to `Pin<&mut &mut S>`,
        // and it needs to add `S: Unpin` bounds to convert `Pin<&mut &mut S>`
        // to `Pin<&mut S>`.
        let stream: Pin<&mut S> = unsafe { self.map_unchecked_mut(|f| f.0) };
        stream.poll_next(cx)
    }
}

let waker = noop_waker();
let mut cx = Context::from_waker(&waker);

let mut stream = AssertUnmoved::new(stream::pending::<()>());

{
    let next = Next(&mut stream);
    let mut pinned_next = Box::pin(next);
    assert!(pinned_next.as_mut().poll(&mut cx).is_pending());
}

// Move stream to the heap.
let mut boxed_stream = Box::pin(stream);

let next = Next(&mut boxed_stream);
let mut pinned_next = Box::pin(next);
// This should panic (as the future is moved).
let _ = pinned_next.as_mut().poll(&mut cx).is_pending();

Optional features

  • futures03 — Implements futures 0.3 traits for assert-unmoved types.
  • tokio02 — Implements tokio 0.2 traits for assert-unmoved types.
  • tokio03 — Implements tokio 0.3 traits for assert-unmoved types.

License

Licensed under either of Apache License, Version 2.0 or MIT license at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.