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