qubit_io/traits/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::Error;
10use std::io::ErrorKind;
11use std::io::Result;
12
13use super::validate_read_count;
14use crate::util::SliceRange;
15
16/// Minimal indexed input interface over items.
17///
18/// `Input` is intentionally smaller and lower-level than [`std::io::Read`]. It
19/// only states that an implementor can read up to `count` items into
20/// `output[index..index + count]`. The caller owns range validation so hot
21/// paths can avoid repeated slicing and bounds checks.
22///
23/// # Coherence note
24///
25/// Every [`std::io::Read`] value automatically implements `Input<Item = u8>`
26/// through the standard I/O integration. Because [`Input::Item`] is an
27/// associated type rather than a trait parameter, a concrete type that
28/// implements [`std::io::Read`] cannot also provide any other direct `Input`
29/// implementation for the same type, including one with a different item type.
30///
31/// Use a wrapper/newtype when a type needs item-oriented input semantics that
32/// differ from its byte-oriented [`std::io::Read`] implementation.
33///
34/// # Examples
35///
36/// Standard readers implement `Input<Item = u8>` automatically, so callers
37/// can use the trait methods directly on them:
38///
39/// ```
40/// use std::io::Cursor;
41///
42/// use qubit_io::Input;
43///
44/// let mut input = Cursor::new(b"data");
45/// let mut output = [0; 4];
46/// assert_eq!(Input::read(&mut input, &mut output).unwrap(), 4);
47/// assert_eq!(&output, b"data");
48/// ```
49pub trait Input {
50 /// The item type read from this input.
51 type Item;
52
53 /// Returns whether this input already buffers items internally.
54 ///
55 /// # Returns
56 ///
57 /// `true` when callers should avoid wrapping this input in another generic
58 /// item buffer automatically.
59 #[inline(always)]
60 #[must_use]
61 fn is_buffered(&self) -> bool {
62 false
63 }
64
65 /// Reads items into an indexed output range without checking the range.
66 ///
67 /// # Parameters
68 ///
69 /// * `output` - Destination storage.
70 /// * `index` - Start index inside `output`.
71 /// * `count` - Maximum number of items to read.
72 ///
73 /// # Returns
74 ///
75 /// The number of items written into `output[index..index + count]`. The
76 /// value must be in `0..=count`.
77 ///
78 /// # Errors
79 ///
80 /// Returns the input error reported by the implementation.
81 ///
82 /// # Safety
83 ///
84 /// The caller must guarantee that `index..index + count` is a valid range
85 /// inside `output` and that the addition does not overflow.
86 unsafe fn read_unchecked(&mut self, output: &mut [Self::Item], index: usize, count: usize) -> Result<usize>;
87
88 /// Reads items into the full output slice.
89 ///
90 /// # Parameters
91 /// - `output`: Destination storage.
92 ///
93 /// # Returns
94 /// The number of items read into `output`.
95 ///
96 /// # Errors
97 ///
98 /// Returns the input error reported by the implementation. Returns
99 /// [`ErrorKind::InvalidData`] if the implementation reports reading more
100 /// items than requested.
101 #[inline(always)]
102 fn read(&mut self, output: &mut [Self::Item]) -> Result<usize> {
103 // SAFETY: The caller ensured the destination slice is valid.
104 let read = unsafe { self.read_unchecked(output, 0, output.len()) }?;
105 validate_read_count(read, output.len())?;
106 Ok(read)
107 }
108
109 /// Reads items into an indexed output range until it is full or EOF is
110 /// reached.
111 ///
112 /// This method retries interrupted reads and treats EOF as a successful
113 /// partial result.
114 ///
115 /// # Parameters
116 ///
117 /// * `output` - Destination storage.
118 /// * `index` - Start index inside `output`.
119 /// * `count` - Maximum number of items to read.
120 ///
121 /// # Returns
122 ///
123 /// The number of items written into `output[index..index + count]`.
124 ///
125 /// # Errors
126 ///
127 /// Returns the first non-[`ErrorKind::Interrupted`] input error. Returns
128 /// [`ErrorKind::InvalidData`] if the implementation reports more items than
129 /// requested.
130 ///
131 /// # Panics
132 ///
133 /// Panics in debug builds if the requested output range does not fit.
134 ///
135 /// # Safety
136 ///
137 /// The caller must guarantee that `index..index + count` is a valid range
138 /// inside `output` and that the addition does not overflow.
139 unsafe fn read_fully_unchecked(&mut self, output: &mut [Self::Item], index: usize, count: usize) -> Result<usize> {
140 debug_assert!(
141 SliceRange::range_fits(output.len(), index, count),
142 "unchecked read-fully range exceeds output buffer"
143 );
144 let mut total = 0;
145 while total < count {
146 let remaining = count - total;
147 // SAFETY: The caller guarantees the original destination range is
148 // valid; `total < count`, so this suffix remains inside it.
149 match unsafe { self.read_unchecked(output, index + total, remaining) } {
150 Ok(0) => break,
151 Ok(read) => {
152 validate_read_count(read, remaining)?;
153 total += read;
154 }
155 Err(error) if error.kind() == ErrorKind::Interrupted => {}
156 Err(error) => return Err(error),
157 }
158 }
159 Ok(total)
160 }
161
162 /// Reads items into the full output slice until it is full or EOF is
163 /// reached.
164 ///
165 /// # Parameters
166 /// - `output`: Destination storage to fill as far as possible.
167 ///
168 /// # Returns
169 /// The number of items written into `output`.
170 ///
171 /// # Errors
172 /// Returns the first non-interrupted input error, or
173 /// [`ErrorKind::InvalidData`] if the implementation reports an impossible
174 /// item count.
175 #[inline(always)]
176 fn read_fully(&mut self, output: &mut [Self::Item]) -> Result<usize> {
177 // SAFETY: The full output slice is a valid destination range.
178 unsafe { self.read_fully_unchecked(output, 0, output.len()) }
179 }
180
181 /// Reads items until the full output slice is filled.
182 ///
183 /// # Parameters
184 ///
185 /// * `output` - Destination storage that must be filled completely.
186 ///
187 /// # Returns
188 ///
189 /// `Ok(())` after the destination has been filled completely.
190 ///
191 /// # Errors
192 ///
193 /// Returns [`ErrorKind::UnexpectedEof`] if the input ends before filling
194 /// `output`. Returns the first non-interrupted input error, or
195 /// [`ErrorKind::InvalidData`] if the implementation reports an impossible
196 /// item count.
197 #[inline]
198 fn read_exactly(&mut self, output: &mut [Self::Item]) -> Result<()> {
199 if self.read_fully(output)? == output.len() {
200 Ok(())
201 } else {
202 Err(Error::new(
203 ErrorKind::UnexpectedEof,
204 "failed to fill whole input buffer",
205 ))
206 }
207 }
208}