Skip to main content

qubit_io/traits/
async_input.rs

1// =============================================================================
2//    Copyright (c) 2026 Haixing Hu.
3//
4//    SPDX-License-Identifier: Apache-2.0
5//
6//    Licensed under the Apache License, Version 2.0.
7// =============================================================================
8
9use std::io::Result;
10use std::pin::Pin;
11use std::task::Context;
12use std::task::Poll;
13
14use crate::ReadExactFuture;
15use crate::ReadFullyFuture;
16use crate::ReadFuture;
17use crate::traits::normalize_async_error;
18use crate::traits::validate_read_count;
19
20/// Minimal runtime-independent asynchronous input interface over items.
21///
22/// `AsyncInput` expresses readiness through [`Poll`] and does not depend on a
23/// particular executor. Implementations may be pinned and therefore are never
24/// moved by the polling methods.
25///
26/// # Examples
27///
28/// Implement the trait for a small in-memory input and inspect its buffering
29/// behavior through the public API:
30///
31/// ```
32/// use std::io::Result;
33/// use std::pin::Pin;
34/// use std::task::Context;
35/// use std::task::Poll;
36/// use std::task::Waker;
37///
38/// use qubit_io::AsyncInput;
39///
40/// struct SliceInput<'a> {
41///     items: &'a [u8],
42///     position: usize,
43/// }
44///
45/// impl AsyncInput for SliceInput<'_> {
46///     type Item = u8;
47///
48///     unsafe fn poll_read_unchecked(
49///         self: Pin<&mut Self>,
50///         _cx: &mut Context<'_>,
51///         output: &mut [Self::Item],
52///         index: usize,
53///         count: usize,
54///     ) -> Poll<Result<usize>> {
55///         let this = self.get_mut();
56///         let available = this.items.len() - this.position;
57///         let read = available.min(count);
58///         output[index..index + read]
59///             .copy_from_slice(&this.items[this.position..this.position + read]);
60///         this.position += read;
61///         Poll::Ready(Ok(read))
62///     }
63/// }
64///
65/// let mut input = SliceInput {
66///     items: b"data",
67///     position: 0,
68/// };
69/// assert!(!input.is_buffered());
70/// let mut cx = Context::from_waker(Waker::noop());
71/// let mut bytes = [0; 4];
72/// assert!(matches!(Pin::new(&mut input).poll_read(&mut cx, &mut bytes),
73///                  Poll::Ready(Ok(4))));
74/// assert_eq!(b"data", &bytes);
75/// assert!(matches!(Pin::new(&mut input).poll_read(&mut cx, &mut bytes),
76///                  Poll::Ready(Ok(0))));
77/// ```
78pub trait AsyncInput {
79    /// The item type read from this input.
80    type Item;
81
82    /// Returns whether this input already buffers items internally.
83    ///
84    /// # Returns
85    ///
86    /// `true` when callers should avoid automatically adding another generic
87    /// item buffer.
88    #[inline(always)]
89    #[must_use]
90    fn is_buffered(&self) -> bool {
91        false
92    }
93
94    /// Polls an indexed read without checking the destination range.
95    ///
96    /// # Parameters
97    ///
98    /// * `cx` - Task context used to register interest when input is pending.
99    /// * `output` - Destination storage.
100    /// * `index` - Start index inside `output`.
101    /// * `count` - Maximum number of items to read.
102    ///
103    /// # Returns
104    ///
105    /// [`Poll::Pending`] when no result is currently available, or a ready I/O
106    /// result containing a count in `0..=count`. A ready zero count denotes
107    /// end of input when `count` is nonzero. A zero `count` must immediately
108    /// return `Poll::Ready(Ok(0))`.
109    ///
110    /// Before returning [`Poll::Pending`], the implementation must arrange for
111    /// `cx`'s waker to be notified when progress may be possible. Neither
112    /// `Poll::Pending` nor `Poll::Ready(Err(_))` may transfer items.
113    /// `WouldBlock` and `Interrupted` must not cross this asynchronous
114    /// boundary; implementations must respectively register readiness or retry
115    /// internally.
116    ///
117    /// # Errors
118    ///
119    /// Returns the input error reported by the implementation.
120    ///
121    /// # Safety
122    ///
123    /// The caller must guarantee that `index..index + count` is a valid range
124    /// inside `output` and that the addition does not overflow.
125    unsafe fn poll_read_unchecked(
126        self: Pin<&mut Self>,
127        cx: &mut Context<'_>,
128        output: &mut [Self::Item],
129        index: usize,
130        count: usize,
131    ) -> Poll<Result<usize>>;
132
133    /// Polls a read into the full destination slice.
134    ///
135    /// # Parameters
136    ///
137    /// * `cx` - Task context used to register interest when input is pending.
138    /// * `output` - Destination storage.
139    ///
140    /// # Returns
141    ///
142    /// [`Poll::Pending`] or a ready result containing the number of items read.
143    ///
144    /// # Errors
145    ///
146    /// The default implementation converts `WouldBlock` and `Interrupted`
147    /// errors from the underlying operation to
148    /// [`std::io::ErrorKind::InvalidData`], retaining the original error in
149    /// the cause chain. Other errors are returned unchanged. It also
150    /// returns `InvalidData` if the implementation reports more items than
151    /// requested.
152    #[inline]
153    fn poll_read(self: Pin<&mut Self>, cx: &mut Context<'_>, output: &mut [Self::Item]) -> Poll<Result<usize>> {
154        if output.is_empty() {
155            return Poll::Ready(Ok(0));
156        }
157        let requested = output.len();
158        // SAFETY: The full output slice is a valid destination range.
159        match unsafe { self.poll_read_unchecked(cx, output, 0, requested) } {
160            Poll::Ready(Ok(read)) => Poll::Ready(validate_read_count(read, requested).map(|()| read)),
161            Poll::Ready(Err(error)) => Poll::Ready(Err(normalize_async_error(error))),
162            Poll::Pending => Poll::Pending,
163        }
164    }
165
166    /// Creates a future that performs one asynchronous read operation.
167    ///
168    /// # Type Parameters
169    ///
170    /// * `'a` - Shared lifetime of the input borrow and destination slice.
171    ///
172    /// # Parameters
173    ///
174    /// * `output` - Destination storage.
175    ///
176    /// # Returns
177    ///
178    /// A future that resolves with the number of items read.
179    ///
180    /// # Errors
181    ///
182    /// The future returns the input error or
183    /// [`std::io::ErrorKind::InvalidData`] if the implementation reports more
184    /// items than requested.
185    #[inline(always)]
186    fn read_async<'a>(&'a mut self, output: &'a mut [Self::Item]) -> ReadFuture<'a, Self>
187    where
188        Self: Sized + Unpin,
189    {
190        ReadFuture::new(Pin::new(self), output)
191    }
192
193    /// Creates a future that fills a destination as far as possible.
194    ///
195    /// The returned future stops when the destination is full or the input
196    /// reports EOF.
197    ///
198    /// # Type Parameters
199    ///
200    /// * `'a` - Shared lifetime of the input borrow and destination slice.
201    ///
202    /// # Parameters
203    ///
204    /// * `output` - Destination storage.
205    ///
206    /// # Returns
207    ///
208    /// A future that resolves with the total number of items read.
209    ///
210    /// # Errors
211    ///
212    /// The future returns the input error or
213    /// [`std::io::ErrorKind::InvalidData`] if the implementation reports more
214    /// items than the remaining destination can hold.
215    #[inline(always)]
216    fn read_fully_async<'a>(&'a mut self, output: &'a mut [Self::Item]) -> ReadFullyFuture<'a, Self>
217    where
218        Self: Sized + Unpin,
219    {
220        ReadFullyFuture::new(Pin::new(self), output)
221    }
222
223    /// Creates a future that fills the entire destination.
224    ///
225    /// The returned future reports [`std::io::ErrorKind::UnexpectedEof`] if
226    /// the input ends before the destination is full.
227    ///
228    /// # Type Parameters
229    ///
230    /// * `'a` - Shared lifetime of the input borrow and destination slice.
231    ///
232    /// # Parameters
233    ///
234    /// * `output` - Destination storage that must be filled completely.
235    ///
236    /// # Returns
237    ///
238    /// A future that resolves after filling `output` or encountering an error.
239    ///
240    /// # Errors
241    ///
242    /// The future returns [`std::io::ErrorKind::UnexpectedEof`] if the input
243    /// ends before `output` is full. It also returns input errors and
244    /// [`std::io::ErrorKind::InvalidData`] if the implementation reports more
245    /// items than the remaining destination can hold.
246    #[inline(always)]
247    fn read_exactly_async<'a>(&'a mut self, output: &'a mut [Self::Item]) -> ReadExactFuture<'a, Self>
248    where
249        Self: Sized + Unpin,
250    {
251        ReadExactFuture::new(Pin::new(self), output)
252    }
253}