Skip to main content

qubit_io/buffered/
buffer.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::ptr;
10
11/// Low-level contiguous storage with a readable window and spare tail capacity.
12///
13/// `Buffer` stores initialized values and tracks a readable window as
14/// `data[position..limit]`. Values before `position` are considered consumed,
15/// and values after `limit` are spare capacity that callers may fill before
16/// advancing the limit.
17///
18/// This type is intentionally a low-level, hot-path API. It exposes the full
19/// backing storage through [`Self::data`] and [`Self::data_mut`] so
20/// higher-level buffering code can avoid repeated slicing and bounds checks.
21/// Callers that mutate the backing storage directly must preserve the `position
22/// <= limit <= capacity` invariant and must only make initialized spare
23/// elements readable by calling [`Self::advance`] or
24/// [`Self::advance_unchecked`].
25///
26/// The unchecked methods are for code that has already validated ranges at a
27/// higher level. They keep debug assertions for development builds, but safe
28/// callers should use [`Self::consume`], [`Self::advance`], and checked slice
29/// operations instead.
30///
31/// # Window model
32///
33/// - `data[..position]` contains consumed elements.
34/// - `data[position..limit]` contains readable elements.
35/// - `data[limit..capacity]` is spare initialized storage.
36///
37/// # Examples
38///
39/// ```
40/// use qubit_io::Buffer;
41///
42/// let mut buffer = Buffer::<u8>::with_capacity(4);
43/// buffer.data_mut()[0..2].copy_from_slice(b"ab");
44/// buffer.advance(2);
45///
46/// assert_eq!(b"ab", &buffer.data()[buffer.position()..buffer.limit()]);
47/// buffer.consume(1);
48/// assert_eq!(b"b", &buffer.data()[buffer.position()..buffer.limit()]);
49/// ```
50#[derive(Clone, Debug)]
51pub struct Buffer<T>
52where
53    T: Copy + Default,
54{
55    data: Vec<T>,
56    position: usize,
57    limit: usize,
58}
59
60impl<T> Buffer<T>
61where
62    T: Copy + Default,
63{
64    /// Creates an empty buffer with at least the requested capacity.
65    ///
66    /// A requested capacity of `0` is raised to `1`.
67    ///
68    /// # Parameters
69    ///
70    /// * `capacity` - Requested element capacity.
71    ///
72    /// # Returns
73    ///
74    /// A buffer with `position == 0` and `limit == 0`.
75    #[inline(always)]
76    #[must_use]
77    pub fn with_capacity(capacity: usize) -> Self {
78        let capacity = capacity.max(1);
79        Self {
80            data: vec![T::default(); capacity],
81            position: 0,
82            limit: 0,
83        }
84    }
85
86    /// Returns the total element capacity.
87    ///
88    /// # Returns
89    ///
90    /// The length of the backing storage.
91    #[inline(always)]
92    #[must_use]
93    pub fn capacity(&self) -> usize {
94        self.data.len()
95    }
96
97    /// Returns the current readable cursor.
98    ///
99    /// # Returns
100    ///
101    /// The start index of the readable window.
102    #[inline(always)]
103    #[must_use]
104    pub const fn position(&self) -> usize {
105        self.position
106    }
107
108    /// Returns the current readable limit.
109    ///
110    /// # Returns
111    ///
112    /// The exclusive end index of the readable window.
113    #[inline(always)]
114    #[must_use]
115    pub const fn limit(&self) -> usize {
116        self.limit
117    }
118
119    /// Returns the backing storage.
120    ///
121    /// # Returns
122    ///
123    /// The full initialized backing slice.
124    #[inline(always)]
125    #[must_use]
126    pub fn data(&self) -> &[T] {
127        &self.data
128    }
129
130    /// Returns the mutable backing storage.
131    ///
132    /// Mutating elements outside the current readable or spare operation may
133    /// invalidate higher-level assumptions about buffered contents.
134    ///
135    /// # Returns
136    ///
137    /// The full initialized backing slice.
138    #[inline(always)]
139    #[must_use]
140    pub fn data_mut(&mut self) -> &mut [T] {
141        &mut self.data
142    }
143
144    /// Returns the number of readable elements.
145    ///
146    /// # Returns
147    ///
148    /// The length of `data[position..limit]`.
149    #[inline(always)]
150    #[must_use]
151    pub const fn available(&self) -> usize {
152        self.limit - self.position
153    }
154
155    /// Returns the number of spare elements after the limit.
156    ///
157    /// # Returns
158    ///
159    /// The length of `data[limit..]`.
160    #[inline(always)]
161    #[must_use]
162    pub fn spare_capacity(&self) -> usize {
163        self.data.len() - self.limit
164    }
165
166    /// Returns whether the readable window is empty.
167    ///
168    /// # Returns
169    ///
170    /// `true` when no elements are available for consumption.
171    #[inline(always)]
172    #[must_use]
173    pub const fn is_empty(&self) -> bool {
174        self.position == self.limit
175    }
176
177    /// Returns whether the spare tail is empty.
178    ///
179    /// # Returns
180    ///
181    /// `true` when `limit == capacity`.
182    #[inline(always)]
183    #[must_use]
184    pub fn is_full(&self) -> bool {
185        self.limit == self.data.len()
186    }
187
188    /// Clears all buffered contents.
189    ///
190    /// This resets both cursors to zero without modifying stored values.
191    #[inline(always)]
192    pub fn clear(&mut self) {
193        self.position = 0;
194        self.limit = 0;
195    }
196
197    /// Advances the readable cursor by `count` elements.
198    ///
199    /// # Parameters
200    ///
201    /// * `count` - Number of readable elements to consume.
202    ///
203    /// # Panics
204    ///
205    /// Panics when `count` exceeds [`Self::available`].
206    #[inline(always)]
207    pub fn consume(&mut self, count: usize) {
208        assert!(
209            count <= self.available(),
210            "cannot consume beyond buffer limit"
211        );
212        // SAFETY: The assertion proves that the new position remains no
213        // greater than the current limit.
214        unsafe {
215            self.consume_unchecked(count);
216        }
217    }
218
219    /// Advances the readable cursor without checking bounds.
220    ///
221    /// # Parameters
222    ///
223    /// * `count` - Number of readable elements to consume.
224    ///
225    /// # Safety
226    ///
227    /// The caller must guarantee that `count <= self.available()`.
228    #[inline(always)]
229    pub unsafe fn consume_unchecked(&mut self, count: usize) {
230        debug_assert!(
231            count <= self.available(),
232            "unchecked consume exceeds available buffer"
233        );
234        self.position += count;
235    }
236
237    /// Advances the readable limit by `count` elements.
238    ///
239    /// # Parameters
240    ///
241    /// * `count` - Number of initialized spare elements to make readable.
242    ///
243    /// # Panics
244    ///
245    /// Panics when `count` exceeds [`Self::spare_capacity`].
246    #[inline(always)]
247    pub fn advance(&mut self, count: usize) {
248        assert!(
249            count <= self.spare_capacity(),
250            "cannot advance beyond buffer capacity"
251        );
252        // SAFETY: The assertion proves that the new limit remains no greater
253        // than the backing storage length.
254        unsafe {
255            self.advance_unchecked(count);
256        }
257    }
258
259    /// Advances the readable limit without checking bounds.
260    ///
261    /// # Parameters
262    ///
263    /// * `count` - Number of initialized spare elements to make readable.
264    ///
265    /// # Safety
266    ///
267    /// The caller must guarantee that `count <= self.spare_capacity()`.
268    #[inline(always)]
269    pub unsafe fn advance_unchecked(&mut self, count: usize) {
270        debug_assert!(
271            count <= self.spare_capacity(),
272            "unchecked advance exceeds spare buffer capacity"
273        );
274        self.limit += count;
275    }
276
277    /// Moves unread elements to the front of the backing storage.
278    ///
279    /// Consumed elements are discarded. The unread element count is preserved,
280    /// and the readable window starts at zero after compaction.
281    #[inline]
282    pub fn compact(&mut self) {
283        let available = self.available();
284        if available == 0 {
285            self.clear();
286            return;
287        }
288        if self.position != 0 {
289            // SAFETY: Source and destination ranges are inside the same
290            // allocation and may overlap, so `copy` is used instead of
291            // `copy_nonoverlapping`.
292            unsafe {
293                let source = self.data.as_ptr().add(self.position);
294                let destination = self.data.as_mut_ptr();
295                ptr::copy(source, destination, available);
296            }
297        }
298        self.position = 0;
299        self.limit = available;
300    }
301
302    /// Copies values from an external slice into the spare tail.
303    ///
304    /// The copied values are made readable by advancing the limit by `count`.
305    ///
306    /// # Parameters
307    ///
308    /// * `input` - Source storage.
309    /// * `input_index` - Start index inside `input`.
310    /// * `count` - Number of values to copy.
311    ///
312    /// # Safety
313    ///
314    /// The caller must guarantee that `input_index..input_index + count` is a
315    /// valid range inside `input`, that the addition does not overflow, that
316    /// `count <= self.spare_capacity()`, and that the source range does not
317    /// overlap with this buffer's destination range.
318    #[inline(always)]
319    pub unsafe fn copy_from_unchecked(
320        &mut self,
321        input: &[T],
322        input_index: usize,
323        count: usize,
324    ) {
325        debug_assert!(
326            input_index
327                .checked_add(count)
328                .is_some_and(|end| end <= input.len()),
329            "unchecked input range exceeds source buffer"
330        );
331        debug_assert!(
332            count <= self.spare_capacity(),
333            "unchecked input copy exceeds spare buffer capacity"
334        );
335        // SAFETY: The caller guarantees the source range and spare destination
336        // range are valid and non-overlapping.
337        unsafe {
338            let source = input.as_ptr().add(input_index);
339            let destination = self.data.as_mut_ptr().add(self.limit);
340            ptr::copy_nonoverlapping(source, destination, count);
341            self.advance_unchecked(count);
342        }
343    }
344
345    /// Copies readable values into an external slice.
346    ///
347    /// The copied values are consumed by advancing the position by `count`.
348    ///
349    /// # Parameters
350    ///
351    /// * `output` - Destination storage.
352    /// * `output_index` - Start index inside `output`.
353    /// * `count` - Number of values to copy.
354    ///
355    /// # Safety
356    ///
357    /// The caller must guarantee that `output_index..output_index + count` is
358    /// a valid range inside `output`, that the addition does not overflow, that
359    /// `count <= self.available()`, and that the source range does not overlap
360    /// with the destination range.
361    #[inline(always)]
362    pub unsafe fn copy_to_unchecked(
363        &mut self,
364        output: &mut [T],
365        output_index: usize,
366        count: usize,
367    ) {
368        debug_assert!(
369            output_index
370                .checked_add(count)
371                .is_some_and(|end| end <= output.len()),
372            "unchecked output range exceeds destination buffer"
373        );
374        debug_assert!(
375            count <= self.available(),
376            "unchecked output copy exceeds readable buffer"
377        );
378        // SAFETY: The caller guarantees the readable source range and
379        // destination range are valid and non-overlapping.
380        unsafe {
381            let source = self.data.as_ptr().add(self.position);
382            let destination = output.as_mut_ptr().add(output_index);
383            ptr::copy_nonoverlapping(source, destination, count);
384            self.consume_unchecked(count);
385        }
386    }
387}