pin_cursor/lib.rs
1//! A simple `!Unpin` I/O backend for async-std, designed for use in tests.
2//!
3//! This crate provides the `PinCursor` struct which wraps around `async_std::io::Cursor`
4//! but is explicitly **not** `Unpin`. It is a little building block to help write tests
5//! where you want to ensure that your own asynchronous IO code behaves correctly when reading from
6//! or writing to something that is *definitely* `!Unpin`.
7//!
8//! - It can be backed by any `Unpin` data buffer that can be slotted into `async_std::io::Cursor`.
9//! Usually `Vec<u8>` or `&mut [u8]` (e. g. from an array) are used.
10//! - It implements `async_std::io::{Read, Write, Seek}`, so you can poll these traits' methods
11//! in your own futures.
12//! - At the same time, it provides several high-level methods through which you can manipulate
13//! the PinCursor in a simple `async {}` block.
14//!
15//! # Examples
16//!
17//! ```
18//! # use async_std::task::block_on;
19//! use pin_cursor::PinCursor;
20//! use async_std::io::{prelude::*, Cursor};
21//! use std::io::SeekFrom;
22//! use std::pin::Pin;
23//!
24//! // Construct a async_std::io::Cursor however you like...
25//! let mut data: Vec<u8> = Vec::new();
26//! let cursor = Cursor::new(&mut data);
27//! // ... then wrap it in PinCursor and a pinned pointer, thus losing the Unpin privileges.
28//! let mut cursor: Pin<Box<PinCursor<_>>> = Box::pin(PinCursor::wrap(cursor));
29//! // Note that we have to make an owning pointer first -
30//! // making a Pin<&mut PinCursor<_>> directly is impossible!
31//! // (There is a more complex way to allocate on stack - see the features section.)
32//!
33//! // Methods of PinCursor mostly return futures and are designed for use in async contexts.
34//! # block_on(
35//! async {
36//! // You can write!
37//! assert_eq!(cursor.as_mut().write(&[1u8, 2u8, 3u8]).await.unwrap(), 3);
38//!
39//! // You can seek!
40//! assert_eq!(cursor.position(), 3);
41//! assert_eq!(cursor.as_mut().seek(SeekFrom::Start(1)).await.unwrap(), 1);
42//! assert_eq!(cursor.position(), 1);
43//!
44//! // You can read!
45//! let mut buf = [0u8; 1];
46//! assert_eq!(cursor.as_mut().read(buf.as_mut()).await.unwrap(), 1);
47//! assert_eq!(buf[0], 2);
48//!
49//! // There's also this way of seeking that doesn't involve futures.
50//! cursor.as_mut().set_position(0);
51//! assert_eq!(cursor.as_mut().read(buf.as_mut()).await.unwrap(), 1);
52//! assert_eq!(buf[0], 1);
53//! }
54//! # );
55//! ```
56//!
57//! # Features
58//!
59//! The optional feature `stackpin` enables integration with [stackpin], a crate that provides
60//! a way to allocate `!Unpin` structures on stack.
61//!
62//! ```ignore
63//! # use pin_cursor::PinCursor;
64//! # use async_std::io::Cursor;
65//! # use std::pin::Pin;
66//! use stackpin::stack_let;
67//!
68//! let mut data: Vec<u8> = vec![1, 2];
69//! stack_let!(mut cursor : PinCursor<_> = Cursor::new(&mut data));
70//! let cursor_ptr: Pin<&mut PinCursor<_>> = Pin::as_mut(&mut cursor);
71//! ```
72//!
73//! Now you have a correctly pinned `PinCursor` that's allocated on stack instead of in a box.
74//!
75//! [stackpin]: https://docs.rs/stackpin/0.0.2
76
77use std::future::Future;
78use std::io::{IoSlice, IoSliceMut, Result, SeekFrom};
79use std::marker::PhantomPinned;
80use std::pin::Pin;
81use std::task::{Context, Poll};
82
83use async_std::io::Cursor;
84use async_std::io::prelude::*;
85use pin_project_lite::pin_project;
86
87#[cfg(feature = "stackpin")]
88mod impl_stackpin;
89
90pin_project! {
91 pub struct PinCursor<T> {
92 c: Cursor<T>,
93 #[pin]
94 _p: PhantomPinned
95 }
96}
97
98impl<T> PinCursor<T>
99 where T: Unpin,
100 Cursor<T>: Write + Read + Seek
101{
102 pub fn wrap(c: Cursor<T>) -> Self {
103 Self { c, _p: PhantomPinned }
104 }
105
106 pub fn unwrap(self) -> Cursor<T> {
107 self.c
108 }
109
110 pub fn position(&self) -> u64 {
111 self.c.position()
112 }
113
114 pub fn set_position(self: Pin<&mut Self>, pos: u64) {
115 self.project().c.set_position(pos)
116 }
117
118 pub fn write<'a>(self: Pin<&'a mut Self>, buf: &'a [u8]) -> impl Future<Output=Result<usize>> + 'a {
119 self.project().c.write(buf)
120 }
121
122 pub fn read<'a>(self: Pin<&'a mut Self>, buf: &'a mut [u8]) -> impl Future<Output=Result<usize>> + 'a {
123 self.project().c.read(buf)
124 }
125
126 pub fn seek(self: Pin<&mut Self>, pos: SeekFrom) -> impl Future<Output=Result<u64>> + '_ {
127 self.project().c.seek(pos)
128 }
129}
130
131impl<T> Read for PinCursor<T>
132 where T: Unpin,
133 Cursor<T>: Read
134{
135 fn poll_read(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8]) -> Poll<Result<usize>> {
136 Pin::new(self.project().c).poll_read(cx, buf)
137 }
138
139 fn poll_read_vectored(self: Pin<&mut Self>, cx: &mut Context<'_>, bufs: &mut [IoSliceMut<'_>]) -> Poll<Result<usize>> {
140 Pin::new(self.project().c).poll_read_vectored(cx, bufs)
141 }
142}
143
144impl<T> Write for PinCursor<T>
145 where T: Unpin,
146 Cursor<T>: Write
147{
148 fn poll_write(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> Poll<Result<usize>> {
149 Pin::new(self.project().c).poll_write(cx, buf)
150 }
151
152 fn poll_write_vectored(self: Pin<&mut Self>, cx: &mut Context<'_>, bufs: &[IoSlice<'_>]) -> Poll<Result<usize>> {
153 Pin::new(self.project().c).poll_write_vectored(cx, bufs)
154 }
155
156 fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<()>> {
157 Pin::new(self.project().c).poll_flush(cx)
158 }
159
160 fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<()>> {
161 Pin::new(self.project().c).poll_close(cx)
162 }
163}
164
165impl<T> Seek for PinCursor<T>
166 where T: Unpin,
167 Cursor<T>: Seek
168{
169 fn poll_seek(self: Pin<&mut Self>, cx: &mut Context<'_>, pos: SeekFrom) -> Poll<Result<u64>> {
170 Pin::new(self.project().c).poll_seek(cx, pos)
171 }
172}
173
174#[cfg(test)]
175mod tests {
176 use static_assertions::{assert_impl_all, assert_not_impl_all};
177
178 use super::*;
179
180 #[test]
181 fn impls() {
182 assert_not_impl_all!(PinCursor<Vec<u8>>: Unpin);
183 assert_impl_all!(PinCursor<Vec<u8>>: Read, Write, Seek);
184 }
185}