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}