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::Error;
10use std::io::ErrorKind;
11use std::io::Result;
12
13use super::validate_write_count;
14use crate::ByteOutput;
15use crate::StdWrite;
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    /// Converts this byte output into a standard library writer.
86    ///
87    /// The conversion only wraps `self`; it performs no I/O, flush, or
88    /// allocation. The wrapped output can be recovered with
89    /// [`StdWrite::into_inner`].
90    ///
91    /// # Examples
92    ///
93    /// ```
94    /// use qubit_io::Output;
95    /// use std::io::Write;
96    ///
97    /// let output = std::io::Cursor::new(Vec::new());
98    /// let mut writer = output.into_std_write();
99    /// writer.write_all(b"hello").unwrap();
100    /// std::io::Write::flush(&mut writer).unwrap();
101    /// let output = writer.into_inner();
102    /// assert_eq!(output.into_inner(), b"hello");
103    /// ```
104    ///
105    /// Non-byte outputs cannot be converted:
106    ///
107    /// ```compile_fail
108    /// use qubit_io::Output;
109    ///
110    /// fn convert<O: Output<Item = char>>(output: O) {
111    ///     let _writer = output.into_std_write();
112    /// }
113    /// ```
114    #[must_use = "the returned writer is the converted output"]
115    #[inline]
116    fn into_std_write(self) -> StdWrite<Self>
117    where
118        Self: Sized + ByteOutput,
119    {
120        StdWrite::new(self)
121    }
122
123    /// Returns whether this output already buffers items internally.
124    ///
125    /// # Returns
126    ///
127    /// `true` when callers should avoid wrapping this output in another generic
128    /// item buffer automatically.
129    #[inline(always)]
130    #[must_use]
131    fn is_buffered(&self) -> bool {
132        false
133    }
134
135    /// Writes items from an indexed input range without checking the range.
136    ///
137    /// # Parameters
138    ///
139    /// * `input` - Source storage.
140    /// * `index` - Start index inside `input`.
141    /// * `count` - Maximum number of items to write.
142    ///
143    /// # Returns
144    ///
145    /// The number of items accepted from `input[index..index + count]`. The
146    /// value must be in `0..=count`. A successful call accepts a prefix of the
147    /// requested range and may be short. For a non-empty request, `Ok(0)`
148    /// reports that no items were accepted in this call.
149    /// [`Output::write_fully`] treats this as [`ErrorKind::WriteZero`];
150    /// already accepted prefixes are not rolled back. For a zero-length
151    /// request, a successful call returns zero, which does not establish
152    /// that later writes cannot make progress. Empty requests are not
153    /// guaranteed to suppress implementation errors or avoid an inner call.
154    ///
155    /// # Errors
156    ///
157    /// Returns the output error reported by the implementation. An
158    /// [`ErrorKind::Interrupted`] result is retried by aggregate write methods
159    /// and therefore means this call made no confirmed transfer. If a valid
160    /// prefix was accepted, return `Ok(n)` for it and report a later error on a
161    /// subsequent call. A later aggregate error does not undo successful
162    /// earlier calls. Errors with applied or uncertain side effects must not
163    /// be represented as retryable `Interrupted` solely to request a retry;
164    /// the adapter or caller must choose a policy for those effects.
165    ///
166    /// # Safety
167    ///
168    /// The caller must guarantee that `index..index + count` is a valid range
169    /// inside `input` and that the addition does not overflow.
170    unsafe fn write_unchecked(&mut self, input: &[Self::Item], index: usize, count: usize) -> Result<usize>;
171
172    /// Writes items from the full input slice.
173    ///
174    /// This method performs at most one write operation and keeps the same
175    /// short-write behavior as [`Output::write_unchecked`].
176    ///
177    /// # Parameters
178    ///
179    /// * `input` - Source items.
180    ///
181    /// # Returns
182    ///
183    /// The number of items accepted from `input`. A successful call accepts a
184    /// prefix and may be short. For a non-empty request, `Ok(0)` reports that
185    /// no items were accepted in this call. [`Output::write_fully`] treats
186    /// this as [`ErrorKind::WriteZero`]; already accepted prefixes are not
187    /// rolled back. For a zero-length request, a successful call returns
188    /// zero, which does not establish that later writes cannot make progress.
189    /// Empty requests are not guaranteed to suppress implementation errors or
190    /// avoid an inner call.
191    ///
192    /// # Errors
193    ///
194    /// Returns the output error reported by the implementation. Returns
195    /// [`ErrorKind::InvalidData`] if the implementation reports accepting more
196    /// items than requested.
197    #[inline(always)]
198    fn write(&mut self, input: &[Self::Item]) -> Result<usize> {
199        // SAFETY: The full input slice is a valid source range.
200        let written = unsafe { self.write_unchecked(input, 0, input.len()) }?;
201        validate_write_count(written, input.len())?;
202        Ok(written)
203    }
204
205    /// Writes all items from an indexed input range.
206    ///
207    /// This method repeatedly calls [`Output::write_unchecked`] until all
208    /// `count` items are accepted. Interrupted writes are retried. A zero
209    /// progress report before the range is complete is converted to
210    /// [`ErrorKind::WriteZero`].
211    ///
212    /// # Parameters
213    ///
214    /// * `input` - Source storage.
215    /// * `index` - Start index inside `input`.
216    /// * `count` - Number of items to write.
217    ///
218    /// # Returns
219    ///
220    /// `Ok(())` after the requested range has been written completely.
221    ///
222    /// # Errors
223    ///
224    /// Returns the output error reported by the implementation. Returns
225    /// [`ErrorKind::WriteZero`] if the implementation accepts zero items before
226    /// the requested range is complete. Returns [`ErrorKind::InvalidData`] if
227    /// the implementation reports accepting more items than requested.
228    ///
229    /// # Panics
230    ///
231    /// Panics in debug builds if the requested input range does not fit.
232    ///
233    /// # Safety
234    ///
235    /// The caller must guarantee that `index..index + count` is a valid range
236    /// inside `input` and that the addition does not overflow.
237    unsafe fn write_fully_unchecked(&mut self, input: &[Self::Item], index: usize, count: usize) -> Result<()> {
238        debug_assert!(
239            SliceRange::range_fits(input.len(), index, count),
240            "unchecked write-fully range exceeds input buffer"
241        );
242        let mut written = 0;
243        while written < count {
244            let remaining = count - written;
245            // SAFETY: The caller guarantees the original source range is valid;
246            // `written < count`, so this suffix remains inside it.
247            match unsafe { self.write_unchecked(input, index + written, remaining) } {
248                Ok(0) => {
249                    return Err(Error::new(ErrorKind::WriteZero, "failed to write whole output range"));
250                }
251                Ok(progress) => {
252                    validate_write_count(progress, remaining)?;
253                    written += progress;
254                }
255                Err(error) if error.kind() == ErrorKind::Interrupted => {}
256                Err(error) => return Err(error),
257            }
258        }
259        Ok(())
260    }
261
262    /// Writes all items from the full input slice.
263    ///
264    /// # Parameters
265    /// - `input`: Source items.
266    ///
267    /// # Returns
268    /// `Ok(())` after every item in `input` has been written.
269    ///
270    /// # Errors
271    /// Returns the output error reported by the implementation. Returns
272    /// [`ErrorKind::WriteZero`] if no progress is made before all items are
273    /// accepted, or [`ErrorKind::InvalidData`] for impossible reported counts.
274    #[inline(always)]
275    fn write_fully(&mut self, input: &[Self::Item]) -> Result<()> {
276        // SAFETY: The full input slice is a valid source range.
277        unsafe { self.write_fully_unchecked(input, 0, input.len()) }
278    }
279
280    /// Writes items from multiple buffers in list order.
281    ///
282    /// The buffers form one logical sequence in list order. `Ok(n)` means that
283    /// the first `n` items of that sequence were written, and `n` must not
284    /// exceed the total requested length. A write may make short progress and
285    /// need not consume every buffer; `n` may cross a buffer boundary.
286    ///
287    /// The default implementation skips empty buffers and calls
288    /// [`Output::write`] once on the first non-empty buffer. It does not
289    /// continue to later buffers after a short write. This is only the default
290    /// strategy, not a limit on overrides. Empty lists and lists whose buffers
291    /// are all empty return `Ok(0)` without performing I/O. Interrupted errors
292    /// are not retried. An error does not undo progress from earlier
293    /// independent calls. An override must uphold the sequence and count
294    /// contract itself; this trait does not validate counts returned by
295    /// arbitrary overrides. Use UFCS when a standard I/O method with the same
296    /// name is also in scope.
297    ///
298    /// # Parameters
299    ///
300    /// - `buffers`: Immutable source slices, in the order to write them.
301    ///
302    /// # Returns
303    ///
304    /// The number of items written from the start of the logical sequence.
305    ///
306    /// # Errors
307    ///
308    /// Returns an error from the output operation. The default implementation
309    /// returns [`ErrorKind::InvalidData`] if the selected buffer reports more
310    /// items than it contains. The standard I/O blanket implementation also
311    /// returns [`ErrorKind::InvalidInput`] if the total length overflows,
312    /// [`ErrorKind::OutOfMemory`] if preparing descriptors fails, or
313    /// [`ErrorKind::InvalidData`] if the standard writer reports more than the
314    /// total capacity. A custom override is responsible for its own checks.
315    ///
316    /// # Examples
317    ///
318    /// A byte-output trait object can use the generic interface. The default
319    /// method need not consume every buffer; an override may use them all.
320    ///
321    /// ```
322    /// use qubit_io::ByteOutput;
323    /// use qubit_io::Output;
324    ///
325    /// let mut bytes = Vec::new();
326    /// let output: &mut dyn ByteOutput = &mut bytes;
327    /// let count = Output::write_vectored(output, &[&b"ab"[..], &b"cd"[..]])?;
328    /// assert!(count <= 4);
329    /// assert_eq!(bytes.as_slice(), &b"abcd"[..count]);
330    /// # Ok::<(), std::io::Error>(())
331    /// ```
332    ///
333    /// A vectored write can stop across a buffer boundary. The returned count
334    /// describes a prefix of the concatenated buffers, so the next request
335    /// starts at the remaining suffix.
336    ///
337    /// ```
338    /// use std::io;
339    /// use std::io::IoSlice;
340    /// use std::io::Write;
341    ///
342    /// use qubit_io::Output;
343    /// use qubit_io::StdWrite;
344    ///
345    /// #[derive(Default)]
346    /// struct PrefixOutput(Vec<u8>);
347    ///
348    /// impl Output for PrefixOutput {
349    ///     type Item = u8;
350    ///
351    ///     unsafe fn write_unchecked(
352    ///         &mut self,
353    ///         _input: &[u8],
354    ///         _index: usize,
355    ///         _count: usize,
356    ///     ) -> io::Result<usize> {
357    ///         panic!("this example must use the vectored override")
358    ///     }
359    ///
360    ///     fn write_vectored(&mut self, buffers: &[&[u8]]) -> io::Result<usize> {
361    ///         let mut remaining = 3_usize;
362    ///         let mut written = 0;
363    ///         for buffer in buffers {
364    ///             let count = remaining.min(buffer.len());
365    ///             self.0.extend_from_slice(&buffer[..count]);
366    ///             written += count;
367    ///             remaining -= count;
368    ///             if remaining == 0 {
369    ///                 break;
370    ///             }
371    ///         }
372    ///         Ok(written)
373    ///     }
374    ///
375    ///     fn flush(&mut self) -> io::Result<()> {
376    ///         Ok(())
377    ///     }
378    /// }
379    ///
380    /// let mut writer = StdWrite::new(PrefixOutput::default());
381    /// let count = Write::write_vectored(
382    ///     &mut writer,
383    ///     &[IoSlice::new(b"ab"), IoSlice::new(b"cd")],
384    /// )?;
385    /// assert_eq!(count, 3);
386    /// assert_eq!(writer.into_inner().0, b"abc");
387    /// // The next request must start at the remaining suffix, b"d".
388    /// # Ok::<(), io::Error>(())
389    /// ```
390    fn write_vectored(&mut self, buffers: &[&[Self::Item]]) -> Result<usize> {
391        let Some(buffer) = buffers.iter().find(|buffer| !buffer.is_empty()) else {
392            return Ok(0);
393        };
394        let count = self.write(buffer)?;
395        validate_write_count(count, buffer.len())?;
396        Ok(count)
397    }
398
399    /// Flushes any internally buffered items.
400    ///
401    /// # Returns
402    ///
403    /// `Ok(())` after all internally buffered items have been flushed.
404    ///
405    /// # Errors
406    ///
407    /// Returns the output error reported by the implementation.
408    fn flush(&mut self) -> Result<()>;
409}