qubit_io/traits/output.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::{
10 Error,
11 ErrorKind,
12 Result,
13};
14
15use super::validate_write_count;
16use crate::util::SliceRange;
17
18/// Minimal indexed output interface over items.
19///
20/// `Output` is intentionally smaller and lower-level than [`std::io::Write`].
21/// Its unchecked primitive writes up to `count` items from `input[index..index
22/// + count]`, plus an explicit flush operation. The caller owns range
23/// validation for unchecked calls so hot paths can avoid repeated slicing and
24/// bounds checks. Safe full-slice writes are available through
25/// [`Output::write`] and complete writes through [`Output::write_fully`].
26///
27/// # Coherence note
28///
29/// Every [`std::io::Write`] value automatically implements `Output<Item = u8>`
30/// through the standard I/O integration. Because [`Output::Item`] is an
31/// associated type rather than a trait parameter, a concrete type that
32/// implements [`std::io::Write`] cannot also provide any other direct `Output`
33/// implementation for the same type, including one with a different item type.
34///
35/// Use a wrapper/newtype when a type needs item-oriented output semantics that
36/// differ from its byte-oriented [`std::io::Write`] implementation.
37///
38/// # Method name overlap
39///
40/// `Output::write` has the same method name as [`std::io::Write::write`]
41/// because both perform a safe single write for their respective abstraction
42/// layer. In generic code where both traits are in scope for the same value,
43/// use fully qualified syntax to choose the intended operation:
44///
45/// ```
46/// use std::io::{
47/// Result,
48/// Write,
49/// };
50///
51/// use qubit_io::Output;
52///
53/// fn flush_buffered<T>(output: &mut T) -> Result<()>
54/// where
55/// T: Output + Write,
56/// {
57/// <T as Output>::flush(output)
58/// }
59///
60/// fn write_all_items<T>(output: &mut T, input: &[u8]) -> Result<()>
61/// where
62/// T: Output<Item = u8> + Write,
63/// {
64/// unsafe { <T as Output>::write_fully_unchecked(output, input, 0, input.len()) }
65/// }
66///
67/// fn write_output_items<T>(output: &mut T, input: &[u8]) -> Result<usize>
68/// where
69/// T: Output<Item = u8> + Write,
70/// {
71/// Output::write(output, input)
72/// }
73///
74/// fn flush_bytes<T>(output: &mut T) -> Result<()>
75/// where
76/// T: Output + Write,
77/// {
78/// Write::flush(output)
79/// }
80/// ```
81pub trait Output {
82 /// The item type written to this output.
83 type Item;
84
85 /// Returns whether this output already buffers items internally.
86 ///
87 /// # Returns
88 ///
89 /// `true` when callers should avoid wrapping this output in another generic
90 /// item buffer automatically.
91 #[inline(always)]
92 #[must_use]
93 fn is_buffered(&self) -> bool {
94 false
95 }
96
97 /// Writes items from an indexed input range without checking the range.
98 ///
99 /// # Parameters
100 ///
101 /// * `input` - Source storage.
102 /// * `index` - Start index inside `input`.
103 /// * `count` - Maximum number of items to write.
104 ///
105 /// # Returns
106 ///
107 /// The number of items accepted from `input[index..index + count]`. The
108 /// value must be in `0..=count`.
109 ///
110 /// # Errors
111 ///
112 /// Returns the output error reported by the implementation.
113 ///
114 /// # Safety
115 ///
116 /// The caller must guarantee that `index..index + count` is a valid range
117 /// inside `input` and that the addition does not overflow.
118 unsafe fn write_unchecked(
119 &mut self,
120 input: &[Self::Item],
121 index: usize,
122 count: usize,
123 ) -> Result<usize>;
124
125 /// Writes items from the full input slice.
126 ///
127 /// This method performs at most one write operation and keeps the same
128 /// short-write behavior as [`Output::write_unchecked`].
129 ///
130 /// # Parameters
131 ///
132 /// * `input` - Source items.
133 ///
134 /// # Returns
135 ///
136 /// The number of items accepted from `input`.
137 ///
138 /// # Errors
139 ///
140 /// Returns the output error reported by the implementation. Returns
141 /// [`ErrorKind::InvalidData`] if the implementation reports accepting more
142 /// items than requested.
143 #[inline(always)]
144 fn write(&mut self, input: &[Self::Item]) -> Result<usize> {
145 // SAFETY: The full input slice is a valid source range.
146 let written = unsafe { self.write_unchecked(input, 0, input.len()) }?;
147 validate_write_count(written, input.len())?;
148 Ok(written)
149 }
150
151 /// Writes all items from an indexed input range.
152 ///
153 /// This method repeatedly calls [`Output::write_unchecked`] until all
154 /// `count` items are accepted. Interrupted writes are retried. A zero
155 /// progress report before the range is complete is converted to
156 /// [`ErrorKind::WriteZero`].
157 ///
158 /// # Parameters
159 ///
160 /// * `input` - Source storage.
161 /// * `index` - Start index inside `input`.
162 /// * `count` - Number of items to write.
163 ///
164 /// # Returns
165 ///
166 /// `Ok(())` after the requested range has been written completely.
167 ///
168 /// # Errors
169 ///
170 /// Returns the output error reported by the implementation. Returns
171 /// [`ErrorKind::WriteZero`] if the implementation accepts zero items before
172 /// the requested range is complete. Returns [`ErrorKind::InvalidData`] if
173 /// the implementation reports accepting more items than requested.
174 ///
175 /// # Panics
176 ///
177 /// Panics in debug builds if the requested input range does not fit.
178 ///
179 /// # Safety
180 ///
181 /// The caller must guarantee that `index..index + count` is a valid range
182 /// inside `input` and that the addition does not overflow.
183 unsafe fn write_fully_unchecked(
184 &mut self,
185 input: &[Self::Item],
186 index: usize,
187 count: usize,
188 ) -> Result<()> {
189 debug_assert!(
190 SliceRange::range_fits(input.len(), index, count),
191 "unchecked write-fully range exceeds input buffer"
192 );
193 let mut written = 0;
194 while written < count {
195 let remaining = count - written;
196 // SAFETY: The caller guarantees the original source range is valid;
197 // `written < count`, so this suffix remains inside it.
198 match unsafe {
199 self.write_unchecked(input, index + written, remaining)
200 } {
201 Ok(0) => {
202 return Err(Error::new(
203 ErrorKind::WriteZero,
204 "failed to write whole output range",
205 ));
206 }
207 Ok(progress) => {
208 validate_write_count(progress, remaining)?;
209 written += progress;
210 }
211 Err(error) if error.kind() == ErrorKind::Interrupted => {}
212 Err(error) => return Err(error),
213 }
214 }
215 Ok(())
216 }
217
218 /// Writes all items from the full input slice.
219 ///
220 /// # Parameters
221 /// - `input`: Source items.
222 ///
223 /// # Returns
224 /// `Ok(())` after every item in `input` has been written.
225 ///
226 /// # Errors
227 /// Returns the output error reported by the implementation. Returns
228 /// [`ErrorKind::WriteZero`] if no progress is made before all items are
229 /// accepted, or [`ErrorKind::InvalidData`] for impossible reported counts.
230 #[inline(always)]
231 fn write_fully(&mut self, input: &[Self::Item]) -> Result<()> {
232 // SAFETY: The full input slice is a valid source range.
233 unsafe { self.write_fully_unchecked(input, 0, input.len()) }
234 }
235
236 /// Flushes any internally buffered items.
237 ///
238 /// # Returns
239 ///
240 /// `Ok(())` after all internally buffered items have been flushed.
241 ///
242 /// # Errors
243 ///
244 /// Returns the output error reported by the implementation.
245 fn flush(&mut self) -> Result<()>;
246}