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}