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.
33pub trait Input {
34 /// The item type read from this input.
35 type Item;
36
37 /// Returns whether this input already buffers items internally.
38 ///
39 /// # Returns
40 ///
41 /// `true` when callers should avoid wrapping this input in another generic
42 /// item buffer automatically.
43 #[inline(always)]
44 #[must_use]
45 fn is_buffered(&self) -> bool {
46 false
47 }
48
49 /// Reads items into an indexed output range without checking the range.
50 ///
51 /// # Parameters
52 ///
53 /// * `output` - Destination storage.
54 /// * `index` - Start index inside `output`.
55 /// * `count` - Maximum number of items to read.
56 ///
57 /// # Returns
58 ///
59 /// The number of items written into `output[index..index + count]`. The
60 /// value must be in `0..=count`.
61 ///
62 /// # Errors
63 ///
64 /// Returns the input error reported by the implementation.
65 ///
66 /// # Safety
67 ///
68 /// The caller must guarantee that `index..index + count` is a valid range
69 /// inside `output` and that the addition does not overflow.
70 unsafe fn read_unchecked(&mut self, output: &mut [Self::Item], index: usize, count: usize) -> Result<usize>;
71
72 /// Reads items into the full output slice.
73 ///
74 /// # Parameters
75 /// - `output`: Destination storage.
76 ///
77 /// # Returns
78 /// The number of items read into `output`.
79 ///
80 /// # Errors
81 ///
82 /// Returns the input error reported by the implementation. Returns
83 /// [`ErrorKind::InvalidData`] if the implementation reports reading more
84 /// items than requested.
85 #[inline(always)]
86 fn read(&mut self, output: &mut [Self::Item]) -> Result<usize> {
87 // SAFETY: The caller ensured the destination slice is valid.
88 let read = unsafe { self.read_unchecked(output, 0, output.len()) }?;
89 validate_read_count(read, output.len())?;
90 Ok(read)
91 }
92
93 /// Reads items into an indexed output range until it is full or EOF is
94 /// reached.
95 ///
96 /// This method retries interrupted reads and treats EOF as a successful
97 /// partial result.
98 ///
99 /// # Parameters
100 ///
101 /// * `output` - Destination storage.
102 /// * `index` - Start index inside `output`.
103 /// * `count` - Maximum number of items to read.
104 ///
105 /// # Returns
106 ///
107 /// The number of items written into `output[index..index + count]`.
108 ///
109 /// # Errors
110 ///
111 /// Returns the first non-[`ErrorKind::Interrupted`] input error. Returns
112 /// [`ErrorKind::InvalidData`] if the implementation reports more items than
113 /// requested.
114 ///
115 /// # Panics
116 ///
117 /// Panics in debug builds if the requested output range does not fit.
118 ///
119 /// # Safety
120 ///
121 /// The caller must guarantee that `index..index + count` is a valid range
122 /// inside `output` and that the addition does not overflow.
123 unsafe fn read_fully_unchecked(&mut self, output: &mut [Self::Item], index: usize, count: usize) -> Result<usize> {
124 debug_assert!(
125 SliceRange::range_fits(output.len(), index, count),
126 "unchecked read-fully range exceeds output buffer"
127 );
128 let mut total = 0;
129 while total < count {
130 let remaining = count - total;
131 // SAFETY: The caller guarantees the original destination range is
132 // valid; `total < count`, so this suffix remains inside it.
133 match unsafe { self.read_unchecked(output, index + total, remaining) } {
134 Ok(0) => break,
135 Ok(read) => {
136 validate_read_count(read, remaining)?;
137 total += read;
138 }
139 Err(error) if error.kind() == ErrorKind::Interrupted => {}
140 Err(error) => return Err(error),
141 }
142 }
143 Ok(total)
144 }
145
146 /// Reads items into the full output slice until it is full or EOF is
147 /// reached.
148 ///
149 /// # Parameters
150 /// - `output`: Destination storage to fill as far as possible.
151 ///
152 /// # Returns
153 /// The number of items written into `output`.
154 ///
155 /// # Errors
156 /// Returns the first non-interrupted input error, or
157 /// [`ErrorKind::InvalidData`] if the implementation reports an impossible
158 /// item count.
159 #[inline(always)]
160 fn read_fully(&mut self, output: &mut [Self::Item]) -> Result<usize> {
161 // SAFETY: The full output slice is a valid destination range.
162 unsafe { self.read_fully_unchecked(output, 0, output.len()) }
163 }
164
165 /// Reads items until the full output slice is filled.
166 ///
167 /// # Parameters
168 ///
169 /// * `output` - Destination storage that must be filled completely.
170 ///
171 /// # Returns
172 ///
173 /// `Ok(())` after the destination has been filled completely.
174 ///
175 /// # Errors
176 ///
177 /// Returns [`ErrorKind::UnexpectedEof`] if the input ends before filling
178 /// `output`. Returns the first non-interrupted input error, or
179 /// [`ErrorKind::InvalidData`] if the implementation reports an impossible
180 /// item count.
181 #[inline]
182 fn read_exactly(&mut self, output: &mut [Self::Item]) -> Result<()> {
183 if self.read_fully(output)? == output.len() {
184 Ok(())
185 } else {
186 Err(Error::new(
187 ErrorKind::UnexpectedEof,
188 "failed to fill whole input buffer",
189 ))
190 }
191 }
192}