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}