assert_unmoved/lib.rs
1// SPDX-License-Identifier: Apache-2.0 OR MIT
2
3/*!
4A type that asserts that the underlying type is not moved after being
5[pinned][pin] and mutably accessed.
6
7This is a rewrite of [futures-test]'s `AssertUnmoved` to allow use in more
8use cases. This also supports traits other than [futures][futures03].
9
10Many of the changes made in this project are also reflected upstream:
11[rust-lang/futures-rs#2148], [rust-lang/futures-rs#2208]
12
13## Examples
14
15An example of detecting incorrect [`Pin::new_unchecked`] use (**should panic**):
16
17```rust,should_panic
18use std::pin::Pin;
19
20use assert_unmoved::AssertUnmoved;
21use futures::{
22 future::{self, Future},
23 task::{noop_waker, Context},
24};
25
26let waker = noop_waker();
27let mut cx = Context::from_waker(&waker);
28
29// First we allocate the future on the stack and poll it.
30let mut future = AssertUnmoved::new(future::pending::<()>());
31let pinned_future = unsafe { Pin::new_unchecked(&mut future) };
32assert!(pinned_future.poll(&mut cx).is_pending());
33
34// Next we move it back to the heap and poll it again. This second call
35// should panic (as the future is moved).
36let mut boxed_future = Box::new(future);
37let pinned_boxed_future = unsafe { Pin::new_unchecked(&mut *boxed_future) };
38let _ = pinned_boxed_future.poll(&mut cx).is_pending();
39```
40
41An example of detecting incorrect [`StreamExt::next`] implementation (**should panic**):
42
43```rust,should_panic
44# #[cfg(not(feature = "futures03"))]
45# fn main() { unimplemented!() }
46# #[cfg(feature = "futures03")]
47# fn main() {
48use std::pin::Pin;
49
50use assert_unmoved::AssertUnmoved;
51use futures::{
52 future::Future,
53 stream::{self, Stream},
54 task::{noop_waker, Context, Poll},
55};
56
57struct Next<'a, S: Stream>(&'a mut S);
58
59impl<S: Stream> Future for Next<'_, S> {
60 type Output = Option<S::Item>;
61
62 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
63 // This is `Pin<&mut Type>` to `Pin<Field>` projection and is unsound
64 // if `S` is not `Unpin` (you can move `S` after `Next` dropped).
65 //
66 // The correct projection is `Pin<&mut Type>` to `Pin<&mut Field>`.
67 // In `Next`, it is `Pin<&mut Next<'_, S>>` to `Pin<&mut &mut S>`,
68 // and it needs to add `S: Unpin` bounds to convert `Pin<&mut &mut S>`
69 // to `Pin<&mut S>`.
70 let stream: Pin<&mut S> = unsafe { self.map_unchecked_mut(|f| f.0) };
71 stream.poll_next(cx)
72 }
73}
74
75let waker = noop_waker();
76let mut cx = Context::from_waker(&waker);
77
78let mut stream = AssertUnmoved::new(stream::pending::<()>());
79
80{
81 let next = Next(&mut stream);
82 let mut pinned_next = Box::pin(next);
83 assert!(pinned_next.as_mut().poll(&mut cx).is_pending());
84}
85
86// Move stream to the heap.
87let mut boxed_stream = Box::pin(stream);
88
89let next = Next(&mut boxed_stream);
90let mut pinned_next = Box::pin(next);
91// This should panic (as the future is moved).
92let _ = pinned_next.as_mut().poll(&mut cx).is_pending();
93# }
94```
95
96## Optional features
97
98- **`futures03`** — Implements [futures v0.3][futures03] traits for assert-unmoved types.
99- **`tokio1`** — Implements [tokio v1][tokio1] traits for assert-unmoved types.
100- **`tokio03`** — Implements [tokio v0.3][tokio03] traits for assert-unmoved types.
101- **`tokio02`** — Implements [tokio v0.2][tokio02] traits for assert-unmoved types.
102
103Note: The MSRV when these features are enabled depends on the MSRV of these crates.
104
105[`Pin::new_unchecked`]: https://doc.rust-lang.org/std/pin/struct.Pin.html#method.new_unchecked
106[`StreamExt::next`]: https://docs.rs/futures/latest/futures/stream/trait.StreamExt.html#method.next
107[futures-test]: https://docs.rs/futures-test
108[futures03]: https://docs.rs/futures/0.3
109[pin]: https://doc.rust-lang.org/std/pin/index.html
110[rust-lang/futures-rs#2148]: https://github.com/rust-lang/futures-rs/pull/2148
111[rust-lang/futures-rs#2208]: https://github.com/rust-lang/futures-rs/pull/2208
112[tokio02]: https://docs.rs/tokio/0.2
113[tokio03]: https://docs.rs/tokio/0.3
114[tokio1]: https://docs.rs/tokio/1
115*/
116
117#![doc(test(
118 no_crate_inject,
119 attr(
120 deny(warnings, rust_2018_idioms, single_use_lifetimes),
121 allow(dead_code, unused_variables)
122 )
123))]
124#![cfg_attr(test, warn(unsafe_op_in_unsafe_fn))] // unsafe_op_in_unsafe_fn requires Rust 1.52
125#![cfg_attr(not(test), allow(unused_unsafe))]
126#![warn(
127 // Lints that may help when writing public library.
128 missing_debug_implementations,
129 missing_docs,
130 clippy::alloc_instead_of_core,
131 clippy::exhaustive_enums,
132 clippy::exhaustive_structs,
133 clippy::impl_trait_in_params,
134 // clippy::missing_inline_in_public_items,
135 // clippy::std_instead_of_alloc,
136 clippy::std_instead_of_core,
137)]
138// docs.rs only (cfg is enabled by docs.rs, not build script)
139#![cfg_attr(docsrs, feature(doc_cfg))]
140
141#[cfg(test)]
142#[path = "gen/assert_impl.rs"]
143mod assert_impl;
144
145mod assert_unmoved;
146pub use crate::assert_unmoved::AssertUnmoved;