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///
37/// use qubit_io::AsyncInput;
38///
39/// struct SliceInput<'a> {
40///     items: &'a [u8],
41///     position: usize,
42/// }
43///
44/// impl AsyncInput for SliceInput<'_> {
45///     type Item = u8;
46///
47///     unsafe fn poll_read_unchecked(
48///         self: Pin<&mut Self>,
49///         _cx: &mut Context<'_>,
50///         output: &mut [Self::Item],
51///         index: usize,
52///         count: usize,
53///     ) -> Poll<Result<usize>> {
54///         let this = self.get_mut();
55///         let available = this.items.len() - this.position;
56///         let read = available.min(count);
57///         output[index..index + read]
58///             .copy_from_slice(&this.items[this.position..this.position + read]);
59///         this.position += read;
60///         Poll::Ready(Ok(read))
61///     }
62/// }
63///
64/// let input = SliceInput {
65///     items: b"data",
66///     position: 0,
67/// };
68/// assert!(!input.is_buffered());
69/// ```
70pub trait AsyncInput {
71    /// The item type read from this input.
72    type Item;
73
74    /// Returns whether this input already buffers items internally.
75    ///
76    /// # Returns
77    ///
78    /// `true` when callers should avoid automatically adding another generic
79    /// item buffer.
80    #[inline(always)]
81    #[must_use]
82    fn is_buffered(&self) -> bool {
83        false
84    }
85
86    /// Polls an indexed read without checking the destination range.
87    ///
88    /// # Parameters
89    ///
90    /// * `cx` - Task context used to register interest when input is pending.
91    /// * `output` - Destination storage.
92    /// * `index` - Start index inside `output`.
93    /// * `count` - Maximum number of items to read.
94    ///
95    /// # Returns
96    ///
97    /// [`Poll::Pending`] when no result is currently available, or a ready I/O
98    /// result containing a count in `0..=count`. A ready zero count denotes
99    /// end of input when `count` is nonzero. A zero `count` must immediately
100    /// return `Poll::Ready(Ok(0))`.
101    ///
102    /// Before returning [`Poll::Pending`], the implementation must arrange for
103    /// `cx`'s waker to be notified when progress may be possible. Neither
104    /// `Poll::Pending` nor `Poll::Ready(Err(_))` may transfer items.
105    /// `WouldBlock` and `Interrupted` must not cross this asynchronous
106    /// boundary; implementations must respectively register readiness or retry
107    /// internally.
108    ///
109    /// # Errors
110    ///
111    /// Returns the input error reported by the implementation.
112    ///
113    /// # Safety
114    ///
115    /// The caller must guarantee that `index..index + count` is a valid range
116    /// inside `output` and that the addition does not overflow.
117    unsafe fn poll_read_unchecked(
118        self: Pin<&mut Self>,
119        cx: &mut Context<'_>,
120        output: &mut [Self::Item],
121        index: usize,
122        count: usize,
123    ) -> Poll<Result<usize>>;
124
125    /// Polls a read into the full destination slice.
126    ///
127    /// # Parameters
128    ///
129    /// * `cx` - Task context used to register interest when input is pending.
130    /// * `output` - Destination storage.
131    ///
132    /// # Returns
133    ///
134    /// [`Poll::Pending`] or a ready result containing the number of items read.
135    ///
136    /// # Errors
137    ///
138    /// Returns the implementation's input error. Returns
139    /// [`std::io::ErrorKind::InvalidData`] if the implementation reports more
140    /// items than requested.
141    #[inline]
142    fn poll_read(self: Pin<&mut Self>, cx: &mut Context<'_>, output: &mut [Self::Item]) -> Poll<Result<usize>> {
143        if output.is_empty() {
144            return Poll::Ready(Ok(0));
145        }
146        let requested = output.len();
147        // SAFETY: The full output slice is a valid destination range.
148        match unsafe { self.poll_read_unchecked(cx, output, 0, requested) } {
149            Poll::Ready(Ok(read)) => Poll::Ready(validate_read_count(read, requested).map(|()| read)),
150            Poll::Ready(Err(error)) => Poll::Ready(Err(normalize_async_error(error))),
151            Poll::Pending => Poll::Pending,
152        }
153    }
154
155    /// Creates a future that performs one asynchronous read operation.
156    ///
157    /// # Type Parameters
158    ///
159    /// * `'a` - Shared lifetime of the input borrow and destination slice.
160    ///
161    /// # Parameters
162    ///
163    /// * `output` - Destination storage.
164    ///
165    /// # Returns
166    ///
167    /// A future that resolves with the number of items read.
168    #[inline(always)]
169    fn read_async<'a>(&'a mut self, output: &'a mut [Self::Item]) -> ReadFuture<'a, Self>
170    where
171        Self: Sized + Unpin,
172    {
173        ReadFuture::new(Pin::new(self), output)
174    }
175
176    /// Creates a future that fills a destination as far as possible.
177    ///
178    /// The returned future stops when the destination is full or the input
179    /// reports EOF.
180    ///
181    /// # Type Parameters
182    ///
183    /// * `'a` - Shared lifetime of the input borrow and destination slice.
184    ///
185    /// # Parameters
186    ///
187    /// * `output` - Destination storage.
188    ///
189    /// # Returns
190    ///
191    /// A future that resolves with the total number of items read.
192    #[inline(always)]
193    fn read_fully_async<'a>(&'a mut self, output: &'a mut [Self::Item]) -> ReadFullyFuture<'a, Self>
194    where
195        Self: Sized + Unpin,
196    {
197        ReadFullyFuture::new(Pin::new(self), output)
198    }
199
200    /// Creates a future that fills the entire destination.
201    ///
202    /// The returned future reports [`std::io::ErrorKind::UnexpectedEof`] if
203    /// the input ends before the destination is full.
204    ///
205    /// # Type Parameters
206    ///
207    /// * `'a` - Shared lifetime of the input borrow and destination slice.
208    ///
209    /// # Parameters
210    ///
211    /// * `output` - Destination storage that must be filled completely.
212    ///
213    /// # Returns
214    ///
215    /// A future that resolves after filling `output` or encountering an error.
216    #[inline(always)]
217    fn read_exactly_async<'a>(&'a mut self, output: &'a mut [Self::Item]) -> ReadExactFuture<'a, Self>
218    where
219        Self: Sized + Unpin,
220    {
221        ReadExactFuture::new(Pin::new(self), output)
222    }
223}