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