Skip to main content

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    Write,
14};
15
16use crate::util::UncheckedSlice;
17
18/// Minimal indexed output interface over items.
19///
20/// `Output` is intentionally smaller and lower-level than [`Write`]. Its
21/// unchecked primitive writes up to `count` items from `input[index..index +
22/// count]`, plus an explicit flush operation. The caller owns range validation
23/// for unchecked calls so hot paths can avoid repeated slicing and bounds
24/// checks. Safe full-slice writes are available through [`Output::write`] and
25/// complete writes through [`Output::write_fully`].
26///
27/// # Coherence note
28///
29/// Every [`Write`] value automatically implements `Output<Item = u8>` through
30/// the blanket impl below. Because [`Output::Item`] is an associated type
31/// rather than a trait parameter, a concrete type that implements [`Write`]
32/// cannot also provide any other direct `Output` implementation for the same
33/// 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 [`Write`] implementation.
37///
38/// # Method name overlap
39///
40/// `Output::write` has the same method name as [`Write::write`] because both
41/// perform a safe single write for their respective abstraction layer. In
42/// generic code where both traits are in scope for the same value, use fully
43/// 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    /// # Errors
165    ///
166    /// Returns the output error reported by the implementation. Returns
167    /// [`ErrorKind::WriteZero`] if the implementation accepts zero items before
168    /// the requested range is complete. Returns [`ErrorKind::InvalidData`] if
169    /// the implementation reports accepting more items than requested.
170    ///
171    /// # Safety
172    ///
173    /// The caller must guarantee that `index..index + count` is a valid range
174    /// inside `input` and that the addition does not overflow.
175    unsafe fn write_fully_unchecked(
176        &mut self,
177        input: &[Self::Item],
178        index: usize,
179        count: usize,
180    ) -> Result<()> {
181        debug_assert!(
182            UncheckedSlice::range_fits(input.len(), index, count),
183            "unchecked write-fully range exceeds input buffer"
184        );
185        let mut written = 0;
186        while written < count {
187            let remaining = count - written;
188            // SAFETY: The caller guarantees the original source range is valid;
189            // `written < count`, so this suffix remains inside it.
190            match unsafe {
191                self.write_unchecked(input, index + written, remaining)
192            } {
193                Ok(0) => {
194                    return Err(Error::new(
195                        ErrorKind::WriteZero,
196                        "failed to write whole output range",
197                    ));
198                }
199                Ok(progress) => {
200                    validate_write_count(progress, remaining)?;
201                    written += progress;
202                }
203                Err(error) if error.kind() == ErrorKind::Interrupted => {}
204                Err(error) => return Err(error),
205            }
206        }
207        Ok(())
208    }
209
210    /// Writes all items from the full input slice.
211    ///
212    /// # Parameters
213    /// - `input`: Source items.
214    ///
215    /// # Errors
216    /// Returns the output error reported by the implementation. Returns
217    /// [`ErrorKind::WriteZero`] if no progress is made before all items are
218    /// accepted, or [`ErrorKind::InvalidData`] for impossible reported counts.
219    #[inline(always)]
220    fn write_fully(&mut self, input: &[Self::Item]) -> Result<()> {
221        // SAFETY: The full input slice is a valid source range.
222        unsafe { self.write_fully_unchecked(input, 0, input.len()) }
223    }
224
225    /// Flushes any internally buffered items.
226    ///
227    /// # Errors
228    ///
229    /// Returns the output error reported by the implementation.
230    fn flush(&mut self) -> Result<()>;
231}
232
233impl<W> Output for W
234where
235    W: Write + ?Sized,
236{
237    type Item = u8;
238
239    /// Writes bytes to a standard [`Write`] value from an indexed range.
240    #[inline(always)]
241    unsafe fn write_unchecked(
242        &mut self,
243        input: &[u8],
244        index: usize,
245        count: usize,
246    ) -> Result<usize> {
247        debug_assert!(
248            UncheckedSlice::range_fits(input.len(), index, count),
249            "unchecked write range exceeds input buffer"
250        );
251        // SAFETY: The caller guarantees that the range is valid inside
252        // `input`.
253        let source = unsafe { UncheckedSlice::subslice(input, index, count) };
254        Write::write(self, source)
255    }
256
257    /// Writes items into the full output slice.
258    #[inline(always)]
259    fn write(&mut self, input: &[Self::Item]) -> Result<usize> {
260        Write::write(self, input)
261    }
262
263    /// Flushes a standard [`Write`] value.
264    #[inline(always)]
265    fn flush(&mut self) -> Result<()> {
266        Write::flush(self)
267    }
268}
269
270/// Validates an item count returned by an output.
271///
272/// # Parameters
273///
274/// * `written` - Item count reported by the output.
275/// * `requested` - Maximum item count requested from the output.
276///
277/// # Errors
278///
279/// Returns [`ErrorKind::InvalidData`] when the output reports more items than
280/// the source range contained.
281#[inline(always)]
282pub fn validate_write_count(written: usize, requested: usize) -> Result<()> {
283    if written > requested {
284        return Err(Error::new(
285            ErrorKind::InvalidData,
286            format!(
287                "writer reported {written} items for a {requested}-item buffer"
288            ),
289        ));
290    }
291    Ok(())
292}