rust_expect/util/
zerocopy.rs

1//! Zero-copy I/O utilities.
2//!
3//! This module provides utilities for minimizing buffer copies during
4//! I/O operations, improving performance for high-throughput terminal
5//! automation.
6//!
7//! # Features
8//!
9//! - `BytesBuffer`: Reference-counted bytes for zero-copy slicing
10//! - `VecWriter`: Efficient vectored write batching
11//! - `BorrowedView`: Borrowed slice views with lifetime tracking
12//!
13//! # Example
14//!
15//! ```rust
16//! use rust_expect::util::zerocopy::{BytesBuffer, VecWriter};
17//!
18//! // Create a buffer with zero-copy slicing
19//! let mut buffer = BytesBuffer::new();
20//! buffer.extend(b"hello world");
21//!
22//! // Slice without copying
23//! let slice = buffer.slice(0..5);
24//! assert_eq!(&slice[..], b"hello");
25//!
26//! // Batch multiple writes
27//! let mut writer = VecWriter::new();
28//! writer.push(&b"hello"[..]);
29//! writer.push(&b" "[..]);
30//! writer.push(&b"world"[..]);
31//! let bytes = writer.freeze();
32//! assert_eq!(&bytes[..], b"hello world");
33//! ```
34
35use std::io::{self, IoSlice, Write};
36use std::ops::{Deref, Range, RangeBounds};
37
38use bytes::{Buf, Bytes, BytesMut};
39
40/// A reference-counted byte buffer that supports zero-copy slicing.
41///
42/// This is a wrapper around `bytes::Bytes` that provides convenient
43/// methods for terminal automation use cases.
44#[derive(Debug, Clone, Default)]
45pub struct BytesBuffer {
46    inner: BytesMut,
47}
48
49impl BytesBuffer {
50    /// Create an empty buffer.
51    #[must_use]
52    pub fn new() -> Self {
53        Self {
54            inner: BytesMut::new(),
55        }
56    }
57
58    /// Create a buffer with the specified capacity.
59    #[must_use]
60    pub fn with_capacity(capacity: usize) -> Self {
61        Self {
62            inner: BytesMut::with_capacity(capacity),
63        }
64    }
65
66    /// Create a buffer from existing bytes.
67    #[must_use]
68    pub fn from_bytes(data: impl Into<Bytes>) -> Self {
69        let bytes: Bytes = data.into();
70        let mut inner = BytesMut::with_capacity(bytes.len());
71        inner.extend_from_slice(&bytes);
72        Self { inner }
73    }
74
75    /// Get the length of the buffer.
76    #[must_use]
77    pub fn len(&self) -> usize {
78        self.inner.len()
79    }
80
81    /// Check if the buffer is empty.
82    #[must_use]
83    pub fn is_empty(&self) -> bool {
84        self.inner.is_empty()
85    }
86
87    /// Get the capacity of the buffer.
88    #[must_use]
89    pub fn capacity(&self) -> usize {
90        self.inner.capacity()
91    }
92
93    /// Extend the buffer with data.
94    pub fn extend(&mut self, data: &[u8]) {
95        self.inner.extend_from_slice(data);
96    }
97
98    /// Reserve additional capacity.
99    pub fn reserve(&mut self, additional: usize) {
100        self.inner.reserve(additional);
101    }
102
103    /// Clear the buffer, retaining capacity.
104    pub fn clear(&mut self) {
105        self.inner.clear();
106    }
107
108    /// Get a zero-copy slice of the buffer.
109    ///
110    /// The returned `Bytes` shares ownership with the original buffer.
111    #[must_use]
112    pub fn slice(&self, range: Range<usize>) -> Bytes {
113        self.inner.clone().freeze().slice(range)
114    }
115
116    /// Get a zero-copy slice using range bounds.
117    #[must_use]
118    pub fn slice_ref<R: RangeBounds<usize>>(&self, range: R) -> Bytes {
119        use std::ops::Bound;
120
121        let start = match range.start_bound() {
122            Bound::Included(&n) => n,
123            Bound::Excluded(&n) => n + 1,
124            Bound::Unbounded => 0,
125        };
126
127        let end = match range.end_bound() {
128            Bound::Included(&n) => n + 1,
129            Bound::Excluded(&n) => n,
130            Bound::Unbounded => self.len(),
131        };
132
133        self.slice(start..end)
134    }
135
136    /// Freeze the buffer into immutable bytes.
137    ///
138    /// This is a zero-copy operation.
139    #[must_use]
140    pub fn freeze(self) -> Bytes {
141        self.inner.freeze()
142    }
143
144    /// Split off the first `at` bytes.
145    ///
146    /// Returns the split-off bytes, leaving the rest in the buffer.
147    pub fn split_to(&mut self, at: usize) -> BytesMut {
148        self.inner.split_to(at)
149    }
150
151    /// Split off bytes at the end.
152    pub fn split_off(&mut self, at: usize) -> BytesMut {
153        self.inner.split_off(at)
154    }
155
156    /// Consume `n` bytes from the front of the buffer.
157    pub fn advance(&mut self, n: usize) {
158        self.inner.advance(n);
159    }
160
161    /// Get an immutable view of the buffer.
162    #[must_use]
163    pub fn as_bytes(&self) -> &[u8] {
164        &self.inner
165    }
166
167    /// Get the buffer as a string (lossy UTF-8 conversion).
168    #[must_use]
169    pub fn as_str_lossy(&self) -> std::borrow::Cow<'_, str> {
170        String::from_utf8_lossy(&self.inner)
171    }
172
173    /// Find a byte pattern in the buffer.
174    #[must_use]
175    pub fn find(&self, needle: &[u8]) -> Option<usize> {
176        self.inner
177            .windows(needle.len())
178            .position(|window| window == needle)
179    }
180
181    /// Find a string in the buffer.
182    #[must_use]
183    pub fn find_str(&self, needle: &str) -> Option<usize> {
184        self.find(needle.as_bytes())
185    }
186
187    /// Get a view of the last `n` bytes.
188    #[must_use]
189    pub fn tail(&self, n: usize) -> &[u8] {
190        let start = self.len().saturating_sub(n);
191        &self.inner[start..]
192    }
193
194    /// Get a view of the first `n` bytes.
195    #[must_use]
196    pub fn head(&self, n: usize) -> &[u8] {
197        let end = n.min(self.len());
198        &self.inner[..end]
199    }
200}
201
202impl Deref for BytesBuffer {
203    type Target = [u8];
204
205    fn deref(&self) -> &Self::Target {
206        &self.inner
207    }
208}
209
210impl AsRef<[u8]> for BytesBuffer {
211    fn as_ref(&self) -> &[u8] {
212        &self.inner
213    }
214}
215
216impl From<Vec<u8>> for BytesBuffer {
217    fn from(vec: Vec<u8>) -> Self {
218        Self {
219            inner: BytesMut::from(&vec[..]),
220        }
221    }
222}
223
224impl From<&[u8]> for BytesBuffer {
225    fn from(slice: &[u8]) -> Self {
226        Self {
227            inner: BytesMut::from(slice),
228        }
229    }
230}
231
232impl From<&str> for BytesBuffer {
233    fn from(s: &str) -> Self {
234        Self::from(s.as_bytes())
235    }
236}
237
238impl Write for BytesBuffer {
239    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
240        self.extend(buf);
241        Ok(buf.len())
242    }
243
244    fn flush(&mut self) -> io::Result<()> {
245        Ok(())
246    }
247}
248
249/// A writer that batches multiple small writes for vectored I/O.
250///
251/// This reduces the number of system calls by accumulating writes
252/// and sending them as a single vectored write operation.
253#[derive(Debug, Default)]
254pub struct VecWriter {
255    chunks: Vec<Bytes>,
256    total_len: usize,
257}
258
259impl VecWriter {
260    /// Create a new vectored writer.
261    #[must_use]
262    pub const fn new() -> Self {
263        Self {
264            chunks: Vec::new(),
265            total_len: 0,
266        }
267    }
268
269    /// Create a vectored writer with the specified chunk capacity.
270    #[must_use]
271    pub fn with_capacity(capacity: usize) -> Self {
272        Self {
273            chunks: Vec::with_capacity(capacity),
274            total_len: 0,
275        }
276    }
277
278    /// Add bytes to the writer.
279    pub fn push(&mut self, data: impl Into<Bytes>) {
280        let bytes: Bytes = data.into();
281        self.total_len += bytes.len();
282        self.chunks.push(bytes);
283    }
284
285    /// Add a slice to the writer (copies into a new Bytes).
286    pub fn push_slice(&mut self, data: &[u8]) {
287        self.push(Bytes::copy_from_slice(data));
288    }
289
290    /// Get the number of chunks.
291    #[must_use]
292    pub const fn chunk_count(&self) -> usize {
293        self.chunks.len()
294    }
295
296    /// Get the total length across all chunks.
297    #[must_use]
298    pub const fn len(&self) -> usize {
299        self.total_len
300    }
301
302    /// Check if the writer is empty.
303    #[must_use]
304    pub const fn is_empty(&self) -> bool {
305        self.total_len == 0
306    }
307
308    /// Clear all chunks.
309    pub fn clear(&mut self) {
310        self.chunks.clear();
311        self.total_len = 0;
312    }
313
314    /// Get the chunks as I/O slices for vectored writes.
315    ///
316    /// The returned slices can be passed to `write_vectored`.
317    #[must_use]
318    pub fn as_io_slices(&self) -> Vec<IoSlice<'_>> {
319        self.chunks.iter().map(|b| IoSlice::new(b)).collect()
320    }
321
322    /// Freeze all chunks into a single contiguous buffer.
323    ///
324    /// This is useful when you need a single contiguous view.
325    #[must_use]
326    pub fn freeze(self) -> Bytes {
327        if self.chunks.len() == 1 {
328            // Fast path: single chunk
329            return self.chunks.into_iter().next().unwrap();
330        }
331
332        let mut buffer = BytesMut::with_capacity(self.total_len);
333        for chunk in self.chunks {
334            buffer.extend_from_slice(&chunk);
335        }
336        buffer.freeze()
337    }
338
339    /// Write all chunks to a writer using vectored I/O.
340    ///
341    /// # Errors
342    ///
343    /// Returns an error if the write fails.
344    pub fn write_to<W: Write>(&self, writer: &mut W) -> io::Result<usize> {
345        let slices = self.as_io_slices();
346        writer.write_vectored(&slices)
347    }
348}
349
350/// A borrowed view of bytes with lifetime tracking.
351///
352/// This provides a way to pass borrowed slices around without
353/// copying, while still allowing for owned data when needed.
354#[derive(Debug)]
355pub enum BorrowedView<'a> {
356    /// A borrowed slice.
357    Borrowed(&'a [u8]),
358    /// Owned bytes (for cases where borrowing isn't possible).
359    Owned(Bytes),
360}
361
362impl<'a> BorrowedView<'a> {
363    /// Create a borrowed view.
364    #[must_use]
365    pub const fn borrowed(data: &'a [u8]) -> Self {
366        Self::Borrowed(data)
367    }
368
369    /// Create an owned view.
370    #[must_use]
371    pub fn owned(data: impl Into<Bytes>) -> Self {
372        Self::Owned(data.into())
373    }
374
375    /// Get the length of the view.
376    #[must_use]
377    pub const fn len(&self) -> usize {
378        match self {
379            Self::Borrowed(b) => b.len(),
380            Self::Owned(b) => b.len(),
381        }
382    }
383
384    /// Check if the view is empty.
385    #[must_use]
386    pub const fn is_empty(&self) -> bool {
387        self.len() == 0
388    }
389
390    /// Get a slice of the view.
391    #[must_use]
392    pub fn as_slice(&self) -> &[u8] {
393        match self {
394            Self::Borrowed(b) => b,
395            Self::Owned(b) => b,
396        }
397    }
398
399    /// Convert to owned bytes.
400    ///
401    /// This may involve copying if the view is borrowed.
402    #[must_use]
403    pub fn into_owned(self) -> Bytes {
404        match self {
405            Self::Borrowed(b) => Bytes::copy_from_slice(b),
406            Self::Owned(b) => b,
407        }
408    }
409}
410
411impl Deref for BorrowedView<'_> {
412    type Target = [u8];
413
414    fn deref(&self) -> &Self::Target {
415        self.as_slice()
416    }
417}
418
419impl AsRef<[u8]> for BorrowedView<'_> {
420    fn as_ref(&self) -> &[u8] {
421        self.as_slice()
422    }
423}
424
425/// Trait for types that can provide a zero-copy view of their contents.
426pub trait ZeroCopySource {
427    /// Get a borrowed view of the data.
428    fn view(&self) -> BorrowedView<'_>;
429
430    /// Get the length of the data.
431    fn len(&self) -> usize {
432        self.view().len()
433    }
434
435    /// Check if the data is empty.
436    fn is_empty(&self) -> bool {
437        self.len() == 0
438    }
439}
440
441impl ZeroCopySource for [u8] {
442    fn view(&self) -> BorrowedView<'_> {
443        BorrowedView::borrowed(self)
444    }
445}
446
447impl ZeroCopySource for Vec<u8> {
448    fn view(&self) -> BorrowedView<'_> {
449        BorrowedView::borrowed(self)
450    }
451}
452
453impl ZeroCopySource for Bytes {
454    fn view(&self) -> BorrowedView<'_> {
455        BorrowedView::Owned(self.clone())
456    }
457}
458
459impl ZeroCopySource for BytesBuffer {
460    fn view(&self) -> BorrowedView<'_> {
461        BorrowedView::borrowed(&self.inner)
462    }
463}
464
465impl ZeroCopySource for str {
466    fn view(&self) -> BorrowedView<'_> {
467        BorrowedView::borrowed(self.as_bytes())
468    }
469}
470
471impl ZeroCopySource for String {
472    fn view(&self) -> BorrowedView<'_> {
473        BorrowedView::borrowed(self.as_bytes())
474    }
475}
476
477/// A read buffer that minimizes copies during async reads.
478///
479/// This is designed to work with Tokio's `ReadBuf` pattern while
480/// allowing for efficient buffer reuse.
481#[derive(Debug)]
482pub struct ReadPool {
483    buffers: Vec<BytesMut>,
484    buffer_size: usize,
485}
486
487impl ReadPool {
488    /// Create a new read pool.
489    #[must_use]
490    pub const fn new(buffer_size: usize) -> Self {
491        Self {
492            buffers: Vec::new(),
493            buffer_size,
494        }
495    }
496
497    /// Get a buffer from the pool, or create a new one.
498    pub fn acquire(&mut self) -> BytesMut {
499        self.buffers
500            .pop()
501            .unwrap_or_else(|| BytesMut::with_capacity(self.buffer_size))
502    }
503
504    /// Return a buffer to the pool for reuse.
505    pub fn release(&mut self, mut buffer: BytesMut) {
506        buffer.clear();
507        // Only keep buffers that haven't grown too large
508        if buffer.capacity() <= self.buffer_size * 2 {
509            self.buffers.push(buffer);
510        }
511    }
512
513    /// Clear the pool, releasing all buffers.
514    pub fn clear(&mut self) {
515        self.buffers.clear();
516    }
517
518    /// Get the number of buffers in the pool.
519    #[must_use]
520    pub const fn available(&self) -> usize {
521        self.buffers.len()
522    }
523}
524
525impl Default for ReadPool {
526    fn default() -> Self {
527        Self::new(8192) // 8KB default buffer size
528    }
529}
530
531#[cfg(test)]
532mod tests {
533    use super::*;
534
535    #[test]
536    fn bytes_buffer_basic() {
537        let mut buffer = BytesBuffer::new();
538        buffer.extend(b"hello");
539        buffer.extend(b" world");
540
541        assert_eq!(buffer.len(), 11);
542        assert_eq!(buffer.as_bytes(), b"hello world");
543    }
544
545    #[test]
546    fn bytes_buffer_slicing() {
547        let mut buffer = BytesBuffer::with_capacity(20);
548        buffer.extend(b"hello world");
549
550        let slice = buffer.slice(0..5);
551        assert_eq!(&slice[..], b"hello");
552
553        let slice = buffer.slice_ref(6..);
554        assert_eq!(&slice[..], b"world");
555    }
556
557    #[test]
558    fn bytes_buffer_find() {
559        let buffer = BytesBuffer::from("the quick brown fox");
560
561        assert_eq!(buffer.find(b"quick"), Some(4));
562        assert_eq!(buffer.find_str("fox"), Some(16));
563        assert_eq!(buffer.find(b"lazy"), None);
564    }
565
566    #[test]
567    fn bytes_buffer_head_tail() {
568        let buffer = BytesBuffer::from("hello world");
569
570        assert_eq!(buffer.head(5), b"hello");
571        assert_eq!(buffer.tail(5), b"world");
572        assert_eq!(buffer.head(100), b"hello world");
573        assert_eq!(buffer.tail(100), b"hello world");
574    }
575
576    #[test]
577    fn vec_writer_basic() {
578        let mut writer = VecWriter::new();
579        writer.push(b"hello".as_slice());
580        writer.push(b" ".as_slice());
581        writer.push(b"world".as_slice());
582
583        assert_eq!(writer.chunk_count(), 3);
584        assert_eq!(writer.len(), 11);
585
586        let bytes = writer.freeze();
587        assert_eq!(&bytes[..], b"hello world");
588    }
589
590    #[test]
591    fn vec_writer_io_slices() {
592        let mut writer = VecWriter::new();
593        writer.push_slice(b"hello");
594        writer.push_slice(b"world");
595
596        let slices = writer.as_io_slices();
597        assert_eq!(slices.len(), 2);
598    }
599
600    #[test]
601    fn borrowed_view() {
602        let data = b"hello world";
603        let borrowed = BorrowedView::borrowed(data);
604
605        assert_eq!(borrowed.len(), 11);
606        assert_eq!(borrowed.as_slice(), data);
607
608        let owned = borrowed.into_owned();
609        assert_eq!(&owned[..], data);
610    }
611
612    #[test]
613    fn zero_copy_source_trait() {
614        let vec: Vec<u8> = b"hello".to_vec();
615        let view = vec.view();
616        assert_eq!(view.len(), 5);
617
618        let string = "world".to_string();
619        let view = string.view();
620        assert_eq!(view.len(), 5);
621    }
622
623    #[test]
624    fn read_pool() {
625        let mut pool = ReadPool::new(4096);
626
627        assert_eq!(pool.available(), 0);
628
629        let buf1 = pool.acquire();
630        assert!(buf1.capacity() >= 4096);
631
632        let buf2 = pool.acquire();
633        pool.release(buf1);
634        assert_eq!(pool.available(), 1);
635
636        let buf3 = pool.acquire();
637        assert_eq!(pool.available(), 0);
638
639        pool.release(buf2);
640        pool.release(buf3);
641        assert_eq!(pool.available(), 2);
642    }
643
644    #[test]
645    fn write_trait() {
646        use std::io::Write;
647
648        let mut buffer = BytesBuffer::new();
649        write!(buffer, "hello {}", 42).unwrap();
650        assert_eq!(buffer.as_str_lossy(), "hello 42");
651    }
652}