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