1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
//! A type that asserts that the underlying type is not moved after being [pinned][pin] //! and mutably accessed. //! //! This is a rewrite of [`futures-test`](https://docs.rs/futures-test)'s //! `AssertUnmoved` to allow use in more use cases. This also supports traits //! other than `futures`. //! //! # Examples //! //! An example of detecting incorrect [`Pin::new_unchecked`] use (**should panic**): //! //! ```rust,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 detecting incorrect [`StreamExt::next`] implementation (**should panic**): //! //! ```rust,should_panic //! # #[cfg(not(feature = "futures03"))] //! # fn main() { unimplemented!() } //! # #[cfg(feature = "futures03")] //! # fn main() { //! 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](https://docs.rs/futures/0.3) traits for `assert-unmoved` types. //! * **`tokio02`** — Implements [`tokio` 0.2](https://docs.rs/tokio/0.2) traits for `assert-unmoved` types. //! * **`tokio03`** — Implements [`tokio` 0.3](https://docs.rs/tokio/0.3) traits for `assert-unmoved` types. //! //! [`Pin::new_unchecked`]: std::pin::Pin::new_unchecked //! [`StreamExt::next`]: https://docs.rs/futures/0.3/futures/stream/trait.StreamExt.html#method.next //! [pin]: core::pin #![doc(html_root_url = "https://docs.rs/assert-unmoved/0.1.0")] #![doc(test( no_crate_inject, attr(deny(warnings, rust_2018_idioms, single_use_lifetimes), allow(dead_code)) ))] #![warn(future_incompatible, rust_2018_idioms, single_use_lifetimes, unreachable_pub)] #![warn(missing_debug_implementations, missing_docs)] #![warn(clippy::all, clippy::default_trait_access)] // mem::take, #[non_exhaustive], and Option::{as_deref, as_deref_mut} require Rust 1.40, // matches! requires Rust 1.42, str::{strip_prefix, strip_suffix} requires Rust 1.45 #![allow( clippy::mem_replace_with_default, clippy::manual_non_exhaustive, clippy::option_as_ref_deref, clippy::match_like_matches_macro, clippy::manual_strip )] mod assert_unmoved; pub use crate::assert_unmoved::AssertUnmoved;