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