Skip to main content

bytesbuf/
lib.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4#![cfg_attr(coverage_nightly, feature(coverage_attribute))]
5#![cfg_attr(docsrs, feature(doc_cfg))]
6
7//! Types for creating and manipulating byte sequences.
8//!
9//! <img src="https://media.githubusercontent.com/media/microsoft/oxidizer/refs/heads/main/crates/bytesbuf/doc/diagrams/Introduction.png" alt="Diagram showing byte sequences inside BytesView and BytesBuf" />
10//!
11//! Types in this crate enable you to operate on logical sequences of bytes stored in memory,
12//! similar to a `&[u8]` but with some key differences:
13//!
14//! * The bytes in a byte sequence are not required to be consecutive in memory.
15//! * The bytes in a byte sequence are always immutable (append-only construction).
16//!
17//! In practical terms, you may think of a byte sequence as a `Vec<Vec<u8>>` whose contents are
18//! treated as one logical sequence of bytes. Byte sequences are created via [`BytesBuf`] and
19//! consumed via [`BytesView`].
20//!
21//! The primary motivation for using byte sequences instead of simple byte slices is to enable
22//! high-performance zero-copy I/O APIs to produce and consume byte sequences with minimal overhead.
23//!
24//! # Consuming Byte Sequences
25//!
26//! A byte sequence is typically consumed by reading its contents from the start. This is done via the
27//! [`BytesView`] type, which is a view over a byte sequence. When reading data, the bytes read are
28//! removed from the front of the view, shrinking it to only the remaining bytes.
29//!
30//! There are many helper methods on this type for easily consuming bytes from the view:
31//!
32//! * [`get_num_le::<T>()`] reads numbers. Big-endian/native-endian variants also exist.
33//! * [`get_byte()`] reads a single byte.
34//! * [`copy_to_slice()`] copies bytes into a provided slice.
35//! * [`copy_to_uninit_slice()`] copies bytes into a provided uninitialized slice.
36//! * [`as_read()`] creates a `std::io::Read` adapter for reading bytes from the view.
37//!
38//! ```
39//! # let memory = bytesbuf::mem::GlobalPool::new();
40//! # let message = BytesView::copied_from_slice(b"1234123412341234", &memory);
41//! use bytesbuf::BytesView;
42//!
43//! fn consume_message(mut message: BytesView) {
44//!     // We read the message and calculate the sum of all the words in it.
45//!     let mut sum: u64 = 0;
46//!
47//!     while !message.is_empty() {
48//!         let word = message.get_num_le::<u64>();
49//!         sum = sum.saturating_add(word);
50//!     }
51//!
52//!     println!("Message received. The sum of all words in the message is {sum}.");
53//! }
54//! # consume_message(message);
55//! ```
56//!
57//! If the helper methods are not sufficient, you can access the byte sequence behind the [`BytesView`]
58//! via byte slices using the following fundamental methods that underpin the convenience methods:
59//!
60//! * [`first_slice()`] returns the first slice of bytes that makes up the byte sequence. The
61//!   length of this slice is determined by the memory layout of the byte sequence and the first slice
62//!   may not contain all the bytes.
63//! * [`advance()`][ViewAdvance] marks bytes from the beginning of [`first_slice()`] as read, shrinking the
64//!   view by the corresponding amount and moving remaining data up to the front.
65//!   When you advance past the slice returned by [`first_slice()`], the next call to [`first_slice()`]
66//!   will return a new slice of bytes starting from the new front position of the view.
67//!
68//! ```
69//! # let memory = bytesbuf::mem::GlobalPool::new();
70//! # let mut bytes = BytesView::copied_from_slice(b"1234123412341234", &memory);
71//! use bytesbuf::BytesView;
72//!
73//! let len = bytes.len();
74//! let mut slice_lengths = Vec::new();
75//!
76//! while !bytes.is_empty() {
77//!     let slice = bytes.first_slice();
78//!     slice_lengths.push(slice.len());
79//!
80//!     // We have completed processing this slice. All we wanted was to know its length.
81//!     // We can now mark this slice as consumed, revealing the next slice for inspection.
82//!     bytes.advance(slice.len());
83//! }
84//!
85//! println!("Inspected a view over {len} bytes with slice lengths: {slice_lengths:?}");
86//! ```
87//!
88//! To reuse a [`BytesView`], clone it before consuming the contents. This is a cheap
89//! zero-copy operation.
90//!
91//! ```
92//! # let memory = bytesbuf::mem::GlobalPool::new();
93//! # let mut bytes = BytesView::copied_from_slice(b"1234123412341234", &memory);
94//! use bytesbuf::BytesView;
95//!
96//! assert_eq!(bytes.len(), 16);
97//!
98//! let mut bytes_clone = bytes.clone();
99//! assert_eq!(bytes_clone.len(), 16);
100//!
101//! // Consume 8 bytes from the front.
102//! _ = bytes_clone.get_num_le::<u64>();
103//! assert_eq!(bytes_clone.len(), 8);
104//!
105//! // Operations on the clone have no effect on the original view.
106//! assert_eq!(bytes.len(), 16);
107//! ```
108//!
109//! # Producing Byte Sequences
110//!
111//! For creating a byte sequence, you first need some memory capacity to put the bytes into. This
112//! means you need a memory provider, which is a type that implements the [`Memory`] trait.
113//!
114//! Obtaining a memory provider is generally straightforward. Simply use the first matching option
115//! from the following list:
116//!
117//! 1. If you are creating byte sequences for the purpose of submitting them to a specific
118//!    object of a known type (e.g. writing them to a `TcpConnection`), the target type will
119//!    typically implement the [`HasMemory`] trait, which gives you a suitable memory
120//!    provider instance via [`HasMemory::memory()`]. Use this as the memory provider - this
121//!    object will give you memory capacity with a configuration that is optimal for
122//!    delivering bytes of data to that specific consumer (e.g. `TcpConnection`).
123//! 1. If you are creating byte sequences as part of usage-neutral data processing, obtain an
124//!    instance of a shared [`GlobalPool`]. In a typical web application, the global memory pool
125//!    is a service exposed by the application framework. In a different context (e.g. example
126//!    or test code with no framework), you can create your own instance via [`GlobalPool::new()`].
127//!
128//! Once you have a memory provider, you can reserve memory from it by calling
129//! [`Memory::reserve()`] on it. This returns a [`BytesBuf`] with at least the requested
130//! number of bytes of memory capacity.
131//!
132//! ```
133//! # struct Connection {}
134//! # impl Connection { fn memory(&self) -> impl Memory { bytesbuf::mem::GlobalPool::new() } }
135//! # let connection = Connection {};
136//! use bytesbuf::mem::Memory;
137//!
138//! let memory = connection.memory();
139//!
140//! let mut buf = memory.reserve(100);
141//! ```
142//!
143//! Now that you have the memory capacity in a [`BytesBuf`], you can fill the memory
144//! capacity with bytes of data. Creating byte sequences in a [`BytesBuf`] is an
145//! append-only process - you can only add data to the end of the buffered sequence.
146//!
147//! There are many helper methods on [`BytesBuf`] for easily appending bytes to the buffer:
148//!
149//! * [`put_num_le::<T>()`] appends numbers. Big-endian/native-endian variants also exist.
150//! * [`put_slice()`] appends a slice of bytes.
151//! * [`put_byte()`] appends a single byte.
152//! * [`put_byte_repeated()`] appends multiple repetitions of a byte.
153//! * [`put_bytes()`] appends an existing [`BytesView`].
154//!
155//! ```
156//! # struct Connection {}
157//! # impl Connection { fn memory(&self) -> impl Memory { bytesbuf::mem::GlobalPool::new() } }
158//! # let connection = Connection {};
159//! use bytesbuf::mem::Memory;
160//!
161//! let memory = connection.memory();
162//!
163//! let mut buf = memory.reserve(100);
164//!
165//! buf.put_num_be(1234_u64);
166//! buf.put_num_be(5678_u64);
167//! buf.put_slice(*b"Hello, world!");
168//! ```
169//!
170//! If the helper methods are not sufficient, you can write contents directly into mutable byte slices
171//! using the fundamental methods that underpin the convenience methods:
172//!
173//! * [`first_unfilled_slice()`] returns a mutable slice of uninitialized bytes from the beginning of the
174//!   buffer's remaining capacity. The length of this slice is determined by the memory layout
175//!   of the buffer and it may not contain all the capacity that has been reserved.
176//! * [`advance()`][BufAdvance] declares that a number of bytes at the beginning of [`first_unfilled_slice()`]
177//!   have been initialized with data and are no longer unused. This will mark these bytes as valid for
178//!   consumption and advance [`first_unfilled_slice()`] to the next slice of unused memory capacity
179//!   if the current slice has been completely filled.
180//!
181//! See `examples/bb_slice_by_slice_write.rs` for an example of how to use these methods.
182//!
183//! If you do not know exactly how much memory you need in advance, you can extend the [`BytesBuf`]
184//! capacity on demand by calling [`BytesBuf::reserve`]. You can use [`remaining_capacity()`]
185//! to identify how much unused memory capacity is available.
186//!
187//! ```
188//! # struct Connection {}
189//! # impl Connection { fn memory(&self) -> impl Memory { bytesbuf::mem::GlobalPool::new() } }
190//! # let connection = Connection {};
191//! use bytesbuf::mem::Memory;
192//!
193//! let memory = connection.memory();
194//!
195//! let mut buf = memory.reserve(100);
196//!
197//! // .. write some data into the buffer ..
198//!
199//! // We discover that we need 80 additional bytes of memory! No problem.
200//! buf.reserve(80, &memory);
201//!
202//! // Remember that a memory provider can always provide more memory than requested.
203//! assert!(buf.capacity() >= 100 + 80);
204//! assert!(buf.remaining_capacity() >= 80);
205//! ```
206//!
207//! When you have written your byte sequence into the memory capacity of the [`BytesBuf`], you can consume
208//! the data in the buffer as a [`BytesView`].
209//!
210//! ```
211//! # struct Connection {}
212//! # impl Connection { fn memory(&self) -> impl Memory { bytesbuf::mem::GlobalPool::new() } }
213//! # let connection = Connection {};
214//! use bytesbuf::mem::Memory;
215//!
216//! let memory = connection.memory();
217//!
218//! let mut buf = memory.reserve(100);
219//!
220//! buf.put_num_be(1234_u64);
221//! buf.put_num_be(5678_u64);
222//! buf.put_slice(*b"Hello, world!");
223//!
224//! let message = buf.consume_all();
225//! ```
226//!
227//! This can be done piece by piece, and you can continue writing to the [`BytesBuf`]
228//! after consuming some already written bytes.
229//!
230//! ```
231//! # struct Connection {}
232//! # impl Connection { fn memory(&self) -> impl Memory { bytesbuf::mem::GlobalPool::new() } }
233//! # let connection = Connection {};
234//! use bytesbuf::mem::Memory;
235//!
236//! let memory = connection.memory();
237//!
238//! let mut buf = memory.reserve(100);
239//!
240//! buf.put_num_be(1234_u64);
241//! buf.put_num_be(5678_u64);
242//!
243//! let first_8_bytes = buf.consume(8);
244//! let second_8_bytes = buf.consume(8);
245//!
246//! buf.put_slice(*b"Hello, world!");
247//!
248//! let final_contents = buf.consume_all();
249//! ```
250//!
251//! If you already have a [`BytesView`] that you want to write into a [`BytesBuf`], call
252//! [`BytesBuf::put_bytes`]. This is a highly efficient zero-copy operation
253//! that reuses the memory capacity of the view you are appending.
254//!
255//! ```
256//! # struct Connection {}
257//! # impl Connection { fn memory(&self) -> impl Memory { bytesbuf::mem::GlobalPool::new() } }
258//! # let connection = Connection {};
259//! use bytesbuf::mem::Memory;
260//!
261//! let memory = connection.memory();
262//!
263//! let mut header_builder = memory.reserve(16);
264//! header_builder.put_num_be(1234_u64);
265//! let header = header_builder.consume_all();
266//!
267//! let mut buf = memory.reserve(128);
268//! buf.put_bytes(header);
269//! buf.put_slice(*b"Hello, world!");
270//! ```
271//!
272//! Note that there is no requirement that the memory capacity of the buffer and the
273//! memory capacity of the view being appended come from the same memory provider. It is valid
274//! to mix and match memory from different providers, though this may disable some optimizations.
275//!
276//! # Implementing Types that Produce or Consume Byte Sequences
277//!
278//! If you are implementing a type that produces or consumes byte sequences, you should
279//! implement the [`HasMemory`] trait to make it possible for the caller to use optimally
280//! configured memory when creating the byte sequences or buffers to use with your type.
281//!
282//! Even if the implementation of your type today is not capable of taking advantage of
283//! optimizations that depend on the memory configuration, it may be capable of doing so
284//! in the future or may, today or in the future, pass the data to another type that
285//! implements [`HasMemory`], which can take advantage of memory optimizations.
286//! Therefore, it is best to implement this trait on all types that consume byte sequences
287//! via [`BytesView`] or produce byte sequences via [`BytesBuf`].
288//!
289//! The recommended implementation strategy for [`HasMemory`] is as follows:
290//!
291//! * If your type always passes a [`BytesView`] or [`BytesBuf`] to another type that
292//!   implements [`HasMemory`], simply forward the memory provider from the other type.
293//! * If your type can take advantage of optimizations enabled by specific memory configurations,
294//!   (e.g. because it uses operating system APIs that unlock better performance when the memory
295//!   is appropriately configured), return a memory provider that performs the necessary
296//!   configuration.
297//! * If your type neither passes anything to another type that implements [`HasMemory`]
298//!   nor can take advantage of optimizations enabled by specific memory configurations, obtain
299//!   an instance of [`GlobalPool`] as a dependency and return it as the memory provider.
300//!
301//! Example of forwarding the memory provider (see `examples/bb_has_memory_forwarding.rs`
302//! for full code):
303//!
304//! ```
305//! # #[derive(Debug)]
306//! # struct ConnectionZeroCounter {
307//! #     connection: Connection,
308//! # }
309//! # use bytesbuf::BytesView;
310//! use bytesbuf::mem::{HasMemory, MemoryShared};
311//!
312//! impl HasMemory for ConnectionZeroCounter {
313//!     fn memory(&self) -> impl MemoryShared {
314//!         self.connection.memory()
315//!     }
316//! }
317//! # #[derive(Debug)] struct Connection;
318//! # impl Connection { fn write(&mut self, mut _message: BytesView) {} }
319//! # impl HasMemory for Connection { fn memory(&self) -> impl MemoryShared { bytesbuf::mem::GlobalPool::new() } }
320//! ```
321//!
322//! Example of returning a memory provider that performs configuration for optimal memory (see
323//! `examples/bb_has_memory_optimizing.rs` for full code):
324//!
325//! ```
326//! # #[derive(Debug)]
327//! # struct UdpConnection {
328//! #     io_context: IoContext,
329//! # }
330//! use bytesbuf::mem::{CallbackMemory, HasMemory, MemoryShared};
331//!
332//! /// Represents the optimal memory configuration for a UDP connection when reserving I/O memory.
333//! const UDP_CONNECTION_OPTIMAL_MEMORY_CONFIGURATION: MemoryConfiguration = MemoryConfiguration {
334//!     requires_page_alignment: false,
335//!     zero_memory_on_release: false,
336//!     requires_registered_memory: true,
337//! };
338//!
339//! impl HasMemory for UdpConnection {
340//!     fn memory(&self) -> impl MemoryShared {
341//!         CallbackMemory::new({
342//!             // Cloning is cheap, as it is a service that shares resources between clones.
343//!             let io_context = self.io_context.clone();
344//!
345//!             move |min_len| {
346//!                 io_context.reserve_io_memory(min_len, UDP_CONNECTION_OPTIMAL_MEMORY_CONFIGURATION)
347//!             }
348//!         })
349//!     }
350//! }
351//!
352//! # use bytesbuf::BytesBuf;
353//! # #[derive(Clone, Debug)]
354//! # struct IoContext;
355//! # impl IoContext {
356//! #     pub fn reserve_io_memory(
357//! #         &self,
358//! #         min_len: usize,
359//! #         _memory_configuration: MemoryConfiguration,
360//! #     ) -> BytesBuf {
361//! #         todo!()
362//! #     }
363//! # }
364//! # struct MemoryConfiguration { requires_page_alignment: bool, zero_memory_on_release: bool, requires_registered_memory: bool }
365//! ```
366//!
367//! Example of returning a global memory pool when the type is agnostic toward memory configuration
368//! (see `examples/bb_has_memory_global.rs` for full code):
369//!
370//! ```
371//! # #[derive(Debug)]
372//! # struct ChecksumCalculator {
373//! #     memory: GlobalPool,
374//! # }
375//! # use bytesbuf::mem::GlobalPool;
376//! use bytesbuf::mem::{HasMemory, MemoryShared};
377//!
378//! impl HasMemory for ChecksumCalculator {
379//!     fn memory(&self) -> impl MemoryShared {
380//!         // Cloning a memory provider is intended to be a cheap operation, reusing resources.
381//!         self.memory.clone()
382//!     }
383//! }
384//! ```
385//!
386//! It is generally expected that all types work with byte sequences using memory from any provider.
387//! It is true that in some cases this may be impossible (e.g. because you are interacting directly
388//! with a device driver that requires the data to be in a specific physical memory module) but
389//! these cases will be rare and must be explicitly documented.
390//!
391//! If your type can take advantage of optimizations enabled by specific memory configurations,
392//! it needs to determine whether a byte sequence actually uses the desired memory configuration.
393//! This can be done by inspecting the provided byte sequence and the memory metadata it exposes.
394//! If the metadata indicates a suitable configuration, the optimal implementation can be used.
395//! Otherwise, the implementation can fall back to a generic implementation that works with any
396//! byte sequence.
397//!
398//! Example of identifying whether a byte sequence uses the optimal memory configuration (see
399//! `examples/bb_optimal_path.rs` for full code):
400//!
401//! ```
402//! # struct Foo;
403//! use bytesbuf::BytesView;
404//!
405//! # impl Foo {
406//! pub fn write(&mut self, message: BytesView) {
407//!     // We now need to identify whether the message actually uses memory that allows us to
408//!     // use the optimal I/O path. There is no requirement that the data passed to us contains
409//!     // only memory with our preferred configuration.
410//!
411//!     let use_optimal_path = message.slices().all(|(_, meta)| {
412//!         // If there is no metadata, the memory is not I/O memory.
413//!         meta.is_some_and(|meta| {
414//!             // If the type of metadata does not match the metadata
415//!             // exposed by the I/O memory provider, the memory is not I/O memory.
416//!             let Some(io_memory_configuration) = meta.downcast_ref::<MemoryConfiguration>()
417//!             else {
418//!                 return false;
419//!             };
420//!
421//!             // If the memory is I/O memory but is not not pre-registered
422//!             // with the operating system, we cannot use the optimal path.
423//!             io_memory_configuration.requires_registered_memory
424//!         })
425//!     });
426//!
427//!     if use_optimal_path {
428//!         self.write_optimal(message);
429//!     } else {
430//!         self.write_fallback(message);
431//!     }
432//! }
433//! # fn write_optimal(&mut self, _message: BytesView) { }
434//! # fn write_fallback(&mut self, _message: BytesView) { }
435//! # }
436//! # struct MemoryConfiguration { requires_registered_memory: bool }
437//! ```
438//!
439//! Note that there is no requirement that a byte sequence consists of homogeneous memory. Different
440//! parts of the byte sequence may come from different memory providers, so all chunks must be
441//! checked for compatibility.
442//!
443//! # Compatibility with the `bytes` Crate
444//!
445//! The popular [`Bytes`] type from the `bytes` crate is often used in the Rust ecosystem to
446//! represent simple byte buffers of consecutive bytes. For compatibility with this commonly used
447//! type, this crate offers conversion methods to translate between [`BytesView`] and [`Bytes`]
448//! when the `bytes-compat` Cargo feature is enabled:
449//!
450//! * `BytesView::to_bytes()` converts a [`BytesView`] into a [`Bytes`] instance. This
451//!   is not always zero-copy because a byte sequence is not guaranteed to be consecutive in memory.
452//!   You are discouraged from using this method in any performance-relevant logic path.
453//! * `BytesView::from(Bytes)` or `let s: BytesView = bytes.into()` converts a [`Bytes`] instance
454//!   into a [`BytesView`]. This is an efficient zero-copy operation that reuses the memory of the
455//!   `Bytes` instance.
456//!
457//! # Static Data
458//!
459//! You may have static data in your logic, such as the names/prefixes of request/response headers:
460//!
461//! ```
462//! const HEADER_PREFIX: &[u8] = b"Unix-Milliseconds: ";
463//! ```
464//!
465//! Optimal processing of static data requires satisfying multiple requirements:
466//!
467//! * We want zero-copy processing when consuming this data.
468//! * We want to use memory that is optimally configured for the context in which the data is
469//!   consumed (e.g. network connection, file, etc).
470//!
471//! The standard pattern here is to use [`OnceLock`] to lazily initialize a [`BytesView`] from
472//! the static data on first use, using memory from a memory provider that is optimal for the
473//! intended usage.
474//!
475//! ```
476//! use std::sync::OnceLock;
477//!
478//! use bytesbuf::BytesView;
479//!
480//! const HEADER_PREFIX: &[u8] = b"Unix-Milliseconds: ";
481//!
482//! // We transform the static data into a BytesView on first use, via OnceLock.
483//! //
484//! // You are expected to reuse this variable as long as the context does not change.
485//! // For example, it is typically fine to share this across multiple network connections
486//! // because they all likely use the same memory configuration. However, writing to files
487//! // may require a different memory configuration for optimality, so you would need a different
488//! // `BytesView` for that. Such details will typically be documented in the API documentation
489//! // of the type that consumes the `BytesView` (e.g. a network connection or a file writer).
490//! let header_prefix = OnceLock::<BytesView>::new();
491//!
492//! for _ in 0..10 {
493//!     let mut connection = Connection::accept();
494//!
495//!     // The static data is transformed into a BytesView on first use, using memory optimally configured
496//!     // for network connections. The underlying principle is that memory optimally configured for one network
497//!     // connection is likely also optimally configured for another network connection, enabling efficient reuse.
498//!     let header_prefix = header_prefix
499//!         .get_or_init(|| BytesView::copied_from_slice(HEADER_PREFIX, &connection.memory()));
500//!
501//!     // Now we can use the `header_prefix` BytesView in the connection logic.
502//!     // Cloning a BytesView is a cheap zero-copy operation.
503//!     connection.write(header_prefix.clone());
504//! }
505//! # struct Connection;
506//! # impl Connection {
507//! #     fn accept() -> Self { Connection }
508//! #     fn memory(&self) -> impl bytesbuf::mem::Memory { bytesbuf::mem::GlobalPool::new() }
509//! #     fn write(&self, _data: BytesView) {}
510//! # }
511//! ```
512//!
513//! Different usages (e.g. file vs network) may require differently configured memory for optimal
514//! performance, so you may need a different `BytesView` if the same static data is to be used
515//! in different contexts.
516//!
517//! # Testing
518//!
519//! For testing purposes, this crate exposes some special-purpose memory providers that are not
520//! optimized for real-world usage but may be useful to test corner cases of byte sequence
521//! processing in your code.
522//!
523//! See the `mem::testing` module for details (requires `test-util` Cargo feature).
524//!
525//! [`get_num_le::<T>()`]: crate::BytesView::get_num_le
526//! [`get_byte()`]: crate::BytesView::get_byte
527//! [`copy_to_slice()`]: crate::BytesView::copy_to_slice
528//! [`copy_to_uninit_slice()`]: crate::BytesView::copy_to_uninit_slice
529//! [`as_read()`]: crate::BytesView::as_read
530//! [`first_slice()`]: crate::BytesView::first_slice
531//! [ViewAdvance]: crate::BytesView::advance
532//! [`put_num_le::<T>()`]: crate::BytesBuf::put_num_le
533//! [`put_slice()`]: crate::BytesBuf::put_slice
534//! [`put_byte()`]: crate::BytesBuf::put_byte
535//! [`put_byte_repeated()`]: crate::BytesBuf::put_byte_repeated
536//! [`put_bytes()`]: crate::BytesBuf::put_bytes
537//! [`first_unfilled_slice()`]: crate::BytesBuf::first_unfilled_slice
538//! [BufAdvance]: crate::BytesBuf::advance
539//! [`Memory`]: crate::mem::Memory
540//! [`Memory::reserve()`]: crate::mem::Memory::reserve
541//! [`HasMemory`]: crate::mem::HasMemory
542//! [`HasMemory::memory()`]: crate::mem::HasMemory::memory
543//! [`GlobalPool`]: crate::mem::GlobalPool
544//! [`Bytes`]: https://docs.rs/bytes/latest/bytes/struct.Bytes.html
545//! [`remaining_capacity()`]: crate::BytesBuf::remaining_capacity
546//! [`OnceLock`]: std::sync::OnceLock
547//! [`GlobalPool::new()`]: crate::mem::GlobalPool::new
548
549#![doc(html_logo_url = "https://media.githubusercontent.com/media/microsoft/oxidizer/refs/heads/main/crates/bytesbuf/logo.png")]
550#![doc(html_favicon_url = "https://media.githubusercontent.com/media/microsoft/oxidizer/refs/heads/main/crates/bytesbuf/favicon.ico")]
551
552// The root level contains "byte sequence" types, whereas the closely related
553// "memory management" types are shoved away into the `mem` module. This is largely
554// for organizational purposes, to help navigate the API documentation better. Both
555// sets of types very often need to be used together, so they are not functionally separate.
556pub mod mem;
557
558mod buf;
559mod buf_put;
560#[cfg(any(test, feature = "bytes-compat"))]
561mod bytes_compat;
562mod constants;
563mod memory_guard;
564mod read_adapter;
565mod span;
566mod span_builder;
567mod vec;
568mod view;
569mod view_get;
570mod write_adapter;
571
572pub use buf::{BytesBuf, BytesBufRemaining, BytesBufVectoredWrite};
573pub use constants::MAX_INLINE_SPANS;
574pub use memory_guard::MemoryGuard;
575pub(crate) use read_adapter::BytesViewReader;
576pub(crate) use span::Span;
577pub(crate) use span_builder::SpanBuilder;
578pub use view::{BytesView, BytesViewSlices};
579pub(crate) use write_adapter::BytesBufWrite;
580
581#[cfg(test)]
582mod testing;