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}